ux-debug.js 252 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484
  1. Ext.define(null, {
  2. override: 'Ext.ux.gauge.needle.Abstract',
  3. compatibility: Ext.isIE10p,
  4. setTransform: function(centerX, centerY, rotation) {
  5. var needleGroup = this.getNeedleGroup();
  6. this.callParent([
  7. centerX,
  8. centerY,
  9. rotation
  10. ]);
  11. needleGroup.set({
  12. transform: getComputedStyle(needleGroup.dom).getPropertyValue('transform')
  13. });
  14. },
  15. updateStyle: function(style) {
  16. var pathElement;
  17. this.callParent([
  18. style
  19. ]);
  20. if (Ext.isObject(style) && 'transform' in style) {
  21. pathElement = this.getNeedlePath();
  22. pathElement.set({
  23. transform: getComputedStyle(pathElement.dom).getPropertyValue('transform')
  24. });
  25. }
  26. }
  27. });
  28. /**
  29. * This is a base class for more advanced "simlets" (simulated servers). A simlet is asked
  30. * to provide a response given a {@link Ext.ux.ajax.SimXhr} instance.
  31. */
  32. Ext.define('Ext.ux.ajax.Simlet', function() {
  33. var urlRegex = /([^?#]*)(#.*)?$/,
  34. dateRegex = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/,
  35. intRegex = /^[+-]?\d+$/,
  36. floatRegex = /^[+-]?\d+\.\d+$/;
  37. function parseParamValue(value) {
  38. var m;
  39. if (Ext.isDefined(value)) {
  40. value = decodeURIComponent(value);
  41. if (intRegex.test(value)) {
  42. value = parseInt(value, 10);
  43. } else if (floatRegex.test(value)) {
  44. value = parseFloat(value);
  45. } else if (!!(m = dateRegex.exec(value))) {
  46. value = new Date(Date.UTC(+m[1], +m[2] - 1, +m[3], +m[4], +m[5], +m[6]));
  47. }
  48. }
  49. return value;
  50. }
  51. return {
  52. alias: 'simlet.basic',
  53. isSimlet: true,
  54. responseProps: [
  55. 'responseText',
  56. 'responseXML',
  57. 'status',
  58. 'statusText',
  59. 'responseHeaders'
  60. ],
  61. /**
  62. * @cfg {String/Function} responseText
  63. */
  64. /**
  65. * @cfg {String/Function} responseXML
  66. */
  67. /**
  68. * @cfg {Object/Function} responseHeaders
  69. */
  70. /**
  71. * @cfg {Number/Function} status
  72. */
  73. status: 200,
  74. /**
  75. * @cfg {String/Function} statusText
  76. */
  77. statusText: 'OK',
  78. constructor: function(config) {
  79. Ext.apply(this, config);
  80. },
  81. doGet: function(ctx) {
  82. return this.handleRequest(ctx);
  83. },
  84. doPost: function(ctx) {
  85. return this.handleRequest(ctx);
  86. },
  87. doRedirect: function(ctx) {
  88. return false;
  89. },
  90. doDelete: function(ctx) {
  91. var me = this,
  92. xhr = ctx.xhr,
  93. records = xhr.options.records;
  94. me.removeFromData(ctx, records);
  95. },
  96. /**
  97. * Performs the action requested by the given XHR and returns an object to be applied
  98. * on to the XHR (containing `status`, `responseText`, etc.). For the most part,
  99. * this is delegated to `doMethod` methods on this class, such as `doGet`.
  100. *
  101. * @param {Ext.ux.ajax.SimXhr} xhr The simulated XMLHttpRequest instance.
  102. * @return {Object} The response properties to add to the XMLHttpRequest.
  103. */
  104. exec: function(xhr) {
  105. var me = this,
  106. ret = {},
  107. method = 'do' + Ext.String.capitalize(xhr.method.toLowerCase()),
  108. // doGet
  109. fn = me[method];
  110. if (fn) {
  111. ret = fn.call(me, me.getCtx(xhr.method, xhr.url, xhr));
  112. } else {
  113. ret = {
  114. status: 405,
  115. statusText: 'Method Not Allowed'
  116. };
  117. }
  118. return ret;
  119. },
  120. getCtx: function(method, url, xhr) {
  121. return {
  122. method: method,
  123. params: this.parseQueryString(url),
  124. url: url,
  125. xhr: xhr
  126. };
  127. },
  128. handleRequest: function(ctx) {
  129. var me = this,
  130. ret = {},
  131. val;
  132. Ext.Array.forEach(me.responseProps, function(prop) {
  133. if (prop in me) {
  134. val = me[prop];
  135. if (Ext.isFunction(val)) {
  136. val = val.call(me, ctx);
  137. }
  138. ret[prop] = val;
  139. }
  140. });
  141. return ret;
  142. },
  143. openRequest: function(method, url, options, async) {
  144. var ctx = this.getCtx(method, url),
  145. redirect = this.doRedirect(ctx),
  146. xhr;
  147. if (options.action === 'destroy') {
  148. method = 'delete';
  149. }
  150. if (redirect) {
  151. xhr = redirect;
  152. } else {
  153. xhr = new Ext.ux.ajax.SimXhr({
  154. mgr: this.manager,
  155. simlet: this,
  156. options: options
  157. });
  158. xhr.open(method, url, async);
  159. }
  160. return xhr;
  161. },
  162. parseQueryString: function(str) {
  163. var m = urlRegex.exec(str),
  164. ret = {},
  165. key, value, pair, parts, i, n;
  166. if (m && m[1]) {
  167. parts = m[1].split('&');
  168. for (i = 0 , n = parts.length; i < n; ++i) {
  169. if ((pair = parts[i].split('='))[0]) {
  170. key = decodeURIComponent(pair.shift());
  171. value = parseParamValue((pair.length > 1) ? pair.join('=') : pair[0]);
  172. if (!(key in ret)) {
  173. ret[key] = value;
  174. } else if (Ext.isArray(ret[key])) {
  175. ret[key].push(value);
  176. } else {
  177. ret[key] = [
  178. ret[key],
  179. value
  180. ];
  181. }
  182. }
  183. }
  184. }
  185. return ret;
  186. },
  187. redirect: function(method, url, params) {
  188. switch (arguments.length) {
  189. case 2:
  190. if (typeof url === 'string') {
  191. break;
  192. };
  193. params = url;
  194. // fall...
  195. // eslint-disable-next-line no-fallthrough
  196. case 1:
  197. url = method;
  198. method = 'GET';
  199. break;
  200. }
  201. if (params) {
  202. url = Ext.urlAppend(url, Ext.Object.toQueryString(params));
  203. }
  204. return this.manager.openRequest(method, url);
  205. },
  206. removeFromData: function(ctx, records) {
  207. var me = this,
  208. data = me.getData(ctx),
  209. model = (ctx.xhr.options.proxy && ctx.xhr.options.proxy.getModel()) || {},
  210. idProperty = model.idProperty || 'id',
  211. i;
  212. Ext.each(records, function(record) {
  213. var id = record.get(idProperty);
  214. for (i = data.length; i-- > 0; ) {
  215. if (data[i][idProperty] === id) {
  216. me.deleteRecord(i);
  217. break;
  218. }
  219. }
  220. });
  221. }
  222. };
  223. }());
  224. /**
  225. * This base class is used to handle data preparation (e.g., sorting, filtering and
  226. * group summary).
  227. */
  228. Ext.define('Ext.ux.ajax.DataSimlet', function() {
  229. function makeSortFn(def, cmp) {
  230. var order = def.direction,
  231. sign = (order && order.toUpperCase() === 'DESC') ? -1 : 1;
  232. return function(leftRec, rightRec) {
  233. var lhs = leftRec[def.property],
  234. rhs = rightRec[def.property],
  235. c = (lhs < rhs) ? -1 : ((rhs < lhs) ? 1 : 0);
  236. if (c || !cmp) {
  237. return c * sign;
  238. }
  239. return cmp(leftRec, rightRec);
  240. };
  241. }
  242. function makeSortFns(defs, cmp) {
  243. var sortFn, i;
  244. for (sortFn = cmp , i = defs && defs.length; i; ) {
  245. sortFn = makeSortFn(defs[--i], sortFn);
  246. }
  247. return sortFn;
  248. }
  249. return {
  250. extend: 'Ext.ux.ajax.Simlet',
  251. buildNodes: function(node, path) {
  252. var me = this,
  253. nodeData = {
  254. data: []
  255. },
  256. len = node.length,
  257. children, i, child, name;
  258. me.nodes[path] = nodeData;
  259. for (i = 0; i < len; ++i) {
  260. nodeData.data.push(child = node[i]);
  261. name = child.text || child.title;
  262. child.id = path ? path + '/' + name : name;
  263. children = child.children;
  264. if (!(child.leaf = !children)) {
  265. delete child.children;
  266. me.buildNodes(children, child.id);
  267. }
  268. }
  269. },
  270. deleteRecord: function(pos) {
  271. if (this.data && typeof this.data !== 'function') {
  272. Ext.Array.removeAt(this.data, pos);
  273. }
  274. },
  275. fixTree: function(ctx, tree) {
  276. var me = this,
  277. node = ctx.params.node,
  278. nodes;
  279. if (!(nodes = me.nodes)) {
  280. me.nodes = nodes = {};
  281. me.buildNodes(tree, '');
  282. }
  283. node = nodes[node];
  284. if (node) {
  285. if (me.node) {
  286. me.node.sortedData = me.sortedData;
  287. me.node.currentOrder = me.currentOrder;
  288. }
  289. me.node = node;
  290. me.data = node.data;
  291. me.sortedData = node.sortedData;
  292. me.currentOrder = node.currentOrder;
  293. } else {
  294. me.data = null;
  295. }
  296. },
  297. getData: function(ctx) {
  298. var me = this,
  299. params = ctx.params,
  300. order = (params.filter || '') + (params.group || '') + '-' + (params.sort || '') + '-' + (params.dir || ''),
  301. tree = me.tree,
  302. dynamicData, data, fields, sortFn, filters;
  303. if (tree) {
  304. me.fixTree(ctx, tree);
  305. }
  306. data = me.data;
  307. if (typeof data === 'function') {
  308. dynamicData = true;
  309. data = data.call(this, ctx);
  310. }
  311. // If order is '--' then it means we had no order passed, due to the string concat above
  312. if (!data || order === '--') {
  313. return data || [];
  314. }
  315. if (!dynamicData && order === me.currentOrder) {
  316. return me.sortedData;
  317. }
  318. ctx.filterSpec = params.filter && Ext.decode(params.filter);
  319. ctx.groupSpec = params.group && Ext.decode(params.group);
  320. fields = params.sort;
  321. if (params.dir) {
  322. fields = [
  323. {
  324. direction: params.dir,
  325. property: fields
  326. }
  327. ];
  328. } else if (params.sort) {
  329. fields = Ext.decode(params.sort);
  330. } else {
  331. fields = null;
  332. }
  333. if (ctx.filterSpec) {
  334. filters = new Ext.util.FilterCollection();
  335. filters.add(this.processFilters(ctx.filterSpec));
  336. data = Ext.Array.filter(data, filters.getFilterFn());
  337. }
  338. sortFn = makeSortFns((ctx.sortSpec = fields));
  339. if (ctx.groupSpec) {
  340. sortFn = makeSortFns([
  341. ctx.groupSpec
  342. ], sortFn);
  343. }
  344. // If a straight Ajax request, data may not be an array.
  345. // If an Array, preserve 'physical' order of raw data...
  346. data = Ext.isArray(data) ? data.slice(0) : data;
  347. if (sortFn) {
  348. Ext.Array.sort(data, sortFn);
  349. }
  350. me.sortedData = data;
  351. me.currentOrder = order;
  352. return data;
  353. },
  354. processFilters: Ext.identityFn,
  355. getPage: function(ctx, data) {
  356. var ret = data,
  357. length = data.length,
  358. start = ctx.params.start || 0,
  359. end = ctx.params.limit ? Math.min(length, start + ctx.params.limit) : length;
  360. if (start || end < length) {
  361. ret = ret.slice(start, end);
  362. }
  363. return ret;
  364. },
  365. getGroupSummary: function(groupField, rows, ctx) {
  366. return rows[0];
  367. },
  368. getSummary: function(ctx, data, page) {
  369. var me = this,
  370. groupField = ctx.groupSpec.property,
  371. accum,
  372. todo = {},
  373. summary = [],
  374. fieldValue, lastFieldValue;
  375. Ext.each(page, function(rec) {
  376. fieldValue = rec[groupField];
  377. todo[fieldValue] = true;
  378. });
  379. function flush() {
  380. if (accum) {
  381. summary.push(me.getGroupSummary(groupField, accum, ctx));
  382. accum = null;
  383. }
  384. }
  385. // data is ordered primarily by the groupField, so one pass can pick up all
  386. // the summaries one at a time.
  387. Ext.each(data, function(rec) {
  388. fieldValue = rec[groupField];
  389. if (lastFieldValue !== fieldValue) {
  390. flush();
  391. lastFieldValue = fieldValue;
  392. }
  393. if (!todo[fieldValue]) {
  394. // if we have even 1 summary, we have summarized all that we need
  395. // (again because data and page are ordered by groupField)
  396. return !summary.length;
  397. }
  398. if (accum) {
  399. accum.push(rec);
  400. } else {
  401. accum = [
  402. rec
  403. ];
  404. }
  405. return true;
  406. });
  407. flush();
  408. // make sure that last pesky summary goes...
  409. return summary;
  410. }
  411. };
  412. }());
  413. /**
  414. * JSON Simlet.
  415. */
  416. Ext.define('Ext.ux.ajax.JsonSimlet', {
  417. extend: 'Ext.ux.ajax.DataSimlet',
  418. alias: 'simlet.json',
  419. doGet: function(ctx) {
  420. var me = this,
  421. data = me.getData(ctx),
  422. page = me.getPage(ctx, data),
  423. reader = ctx.xhr.options.proxy && ctx.xhr.options.proxy.getReader(),
  424. root = reader && reader.getRootProperty(),
  425. ret = me.callParent(arguments),
  426. // pick up status/statusText
  427. response = {};
  428. if (root && Ext.isArray(page)) {
  429. response[root] = page;
  430. response[reader.getTotalProperty()] = data.length;
  431. } else {
  432. response = page;
  433. }
  434. if (ctx.groupSpec) {
  435. response.summaryData = me.getSummary(ctx, data, page);
  436. }
  437. ret.responseText = Ext.encode(response);
  438. return ret;
  439. },
  440. doPost: function(ctx) {
  441. return this.doGet(ctx);
  442. }
  443. });
  444. /**
  445. * Pivot Simlet does remote pivot calculations.
  446. * Filtering the pivot results doesn't work.
  447. */
  448. Ext.define('Ext.ux.ajax.PivotSimlet', {
  449. extend: 'Ext.ux.ajax.JsonSimlet',
  450. alias: 'simlet.pivot',
  451. lastPost: null,
  452. // last Ajax params sent to this simlet
  453. lastResponse: null,
  454. // last JSON response produced by this simlet
  455. keysSeparator: '',
  456. grandTotalKey: '',
  457. doPost: function(ctx) {
  458. var me = this,
  459. ret = me.callParent(arguments);
  460. // pick up status/statusText
  461. me.lastResponse = me.processData(me.getData(ctx), Ext.decode(ctx.xhr.body));
  462. ret.responseText = Ext.encode(me.lastResponse);
  463. return ret;
  464. },
  465. processData: function(data, params) {
  466. var me = this,
  467. len = data.length,
  468. response = {
  469. success: true,
  470. leftAxis: [],
  471. topAxis: [],
  472. results: []
  473. },
  474. leftAxis = new Ext.util.MixedCollection(),
  475. topAxis = new Ext.util.MixedCollection(),
  476. results = new Ext.util.MixedCollection(),
  477. i, j, k, leftKeys, topKeys, item, agg;
  478. me.lastPost = params;
  479. me.keysSeparator = params.keysSeparator;
  480. me.grandTotalKey = params.grandTotalKey;
  481. for (i = 0; i < len; i++) {
  482. leftKeys = me.extractValues(data[i], params.leftAxis, leftAxis);
  483. topKeys = me.extractValues(data[i], params.topAxis, topAxis);
  484. // add record to grand totals
  485. me.addResult(data[i], me.grandTotalKey, me.grandTotalKey, results);
  486. for (j = 0; j < leftKeys.length; j++) {
  487. // add record to col grand totals
  488. me.addResult(data[i], leftKeys[j], me.grandTotalKey, results);
  489. // add record to left/top keys pair
  490. for (k = 0; k < topKeys.length; k++) {
  491. me.addResult(data[i], leftKeys[j], topKeys[k], results);
  492. }
  493. }
  494. // add record to row grand totals
  495. for (j = 0; j < topKeys.length; j++) {
  496. me.addResult(data[i], me.grandTotalKey, topKeys[j], results);
  497. }
  498. }
  499. // extract items from their left/top collections and build the json response
  500. response.leftAxis = leftAxis.getRange();
  501. response.topAxis = topAxis.getRange();
  502. len = results.getCount();
  503. for (i = 0; i < len; i++) {
  504. item = results.getAt(i);
  505. item.values = {};
  506. for (j = 0; j < params.aggregate.length; j++) {
  507. agg = params.aggregate[j];
  508. item.values[agg.id] = me[agg.aggregator](item.records, agg.dataIndex, item.leftKey, item.topKey);
  509. }
  510. delete (item.records);
  511. response.results.push(item);
  512. }
  513. leftAxis.clear();
  514. topAxis.clear();
  515. results.clear();
  516. return response;
  517. },
  518. getKey: function(value) {
  519. var me = this;
  520. me.keysMap = me.keysMap || {};
  521. if (!Ext.isDefined(me.keysMap[value])) {
  522. me.keysMap[value] = Ext.id();
  523. }
  524. return me.keysMap[value];
  525. },
  526. extractValues: function(record, dimensions, col) {
  527. var len = dimensions.length,
  528. keys = [],
  529. j, key, item, dim;
  530. key = '';
  531. for (j = 0; j < len; j++) {
  532. dim = dimensions[j];
  533. key += (j > 0 ? this.keysSeparator : '') + this.getKey(record[dim.dataIndex]);
  534. item = col.getByKey(key);
  535. if (!item) {
  536. item = col.add(key, {
  537. key: key,
  538. value: record[dim.dataIndex],
  539. dimensionId: dim.id
  540. });
  541. }
  542. keys.push(key);
  543. }
  544. return keys;
  545. },
  546. addResult: function(record, leftKey, topKey, results) {
  547. var item = results.getByKey(leftKey + '/' + topKey);
  548. if (!item) {
  549. item = results.add(leftKey + '/' + topKey, {
  550. leftKey: leftKey,
  551. topKey: topKey,
  552. records: []
  553. });
  554. }
  555. item.records.push(record);
  556. },
  557. sum: function(records, measure, rowGroupKey, colGroupKey) {
  558. var length = records.length,
  559. total = 0,
  560. i;
  561. for (i = 0; i < length; i++) {
  562. total += Ext.Number.from(records[i][measure], 0);
  563. }
  564. return total;
  565. },
  566. avg: function(records, measure, rowGroupKey, colGroupKey) {
  567. var length = records.length,
  568. total = 0,
  569. i;
  570. for (i = 0; i < length; i++) {
  571. total += Ext.Number.from(records[i][measure], 0);
  572. }
  573. return length > 0 ? (total / length) : 0;
  574. },
  575. min: function(records, measure, rowGroupKey, colGroupKey) {
  576. var data = [],
  577. length = records.length,
  578. i, v;
  579. for (i = 0; i < length; i++) {
  580. data.push(records[i][measure]);
  581. }
  582. v = Ext.Array.min(data);
  583. return v;
  584. },
  585. max: function(records, measure, rowGroupKey, colGroupKey) {
  586. var data = [],
  587. length = records.length,
  588. i, v;
  589. for (i = 0; i < length; i++) {
  590. data.push(records[i][measure]);
  591. }
  592. v = Ext.Array.max(data);
  593. return v;
  594. },
  595. count: function(records, measure, rowGroupKey, colGroupKey) {
  596. return records.length;
  597. },
  598. variance: function(records, measure, rowGroupKey, colGroupKey) {
  599. var me = Ext.pivot.Aggregators,
  600. length = records.length,
  601. avg = me.avg.apply(me, arguments),
  602. total = 0,
  603. i;
  604. if (avg > 0) {
  605. for (i = 0; i < length; i++) {
  606. total += Math.pow(Ext.Number.from(records[i][measure], 0) - avg, 2);
  607. }
  608. }
  609. return (total > 0 && length > 1) ? (total / (length - 1)) : 0;
  610. },
  611. varianceP: function(records, measure, rowGroupKey, colGroupKey) {
  612. var me = Ext.pivot.Aggregators,
  613. length = records.length,
  614. avg = me.avg.apply(me, arguments),
  615. total = 0,
  616. i;
  617. if (avg > 0) {
  618. for (i = 0; i < length; i++) {
  619. total += Math.pow(Ext.Number.from(records[i][measure], 0) - avg, 2);
  620. }
  621. }
  622. return (total > 0 && length > 0) ? (total / length) : 0;
  623. },
  624. stdDev: function(records, measure, rowGroupKey, colGroupKey) {
  625. var me = Ext.pivot.Aggregators,
  626. v = me.variance.apply(me, arguments);
  627. return v > 0 ? Math.sqrt(v) : 0;
  628. },
  629. stdDevP: function(records, measure, rowGroupKey, colGroupKey) {
  630. var me = Ext.pivot.Aggregators,
  631. v = me.varianceP.apply(me, arguments);
  632. return v > 0 ? Math.sqrt(v) : 0;
  633. }
  634. });
  635. /**
  636. * Simulates an XMLHttpRequest object's methods and properties but is backed by a
  637. * {@link Ext.ux.ajax.Simlet} instance that provides the data.
  638. */
  639. Ext.define('Ext.ux.ajax.SimXhr', {
  640. readyState: 0,
  641. mgr: null,
  642. simlet: null,
  643. constructor: function(config) {
  644. var me = this;
  645. Ext.apply(me, config);
  646. me.requestHeaders = {};
  647. },
  648. abort: function() {
  649. var me = this;
  650. if (me.timer) {
  651. Ext.undefer(me.timer);
  652. me.timer = null;
  653. }
  654. me.aborted = true;
  655. },
  656. getAllResponseHeaders: function() {
  657. var headers = [];
  658. if (Ext.isObject(this.responseHeaders)) {
  659. Ext.Object.each(this.responseHeaders, function(name, value) {
  660. headers.push(name + ': ' + value);
  661. });
  662. }
  663. return headers.join('\r\n');
  664. },
  665. getResponseHeader: function(header) {
  666. var headers = this.responseHeaders;
  667. return (headers && headers[header]) || null;
  668. },
  669. open: function(method, url, async, user, password) {
  670. var me = this;
  671. me.method = method;
  672. me.url = url;
  673. me.async = async !== false;
  674. me.user = user;
  675. me.password = password;
  676. me.setReadyState(1);
  677. },
  678. overrideMimeType: function(mimeType) {
  679. this.mimeType = mimeType;
  680. },
  681. schedule: function() {
  682. var me = this,
  683. delay = me.simlet.delay || me.mgr.delay;
  684. if (delay) {
  685. me.timer = Ext.defer(function() {
  686. me.onTick();
  687. }, delay);
  688. } else {
  689. me.onTick();
  690. }
  691. },
  692. send: function(body) {
  693. var me = this;
  694. me.body = body;
  695. if (me.async) {
  696. me.schedule();
  697. } else {
  698. me.onComplete();
  699. }
  700. },
  701. setReadyState: function(state) {
  702. var me = this;
  703. if (me.readyState !== state) {
  704. me.readyState = state;
  705. me.onreadystatechange();
  706. }
  707. },
  708. setRequestHeader: function(header, value) {
  709. this.requestHeaders[header] = value;
  710. },
  711. // handlers
  712. onreadystatechange: Ext.emptyFn,
  713. onComplete: function() {
  714. var me = this,
  715. callback, text;
  716. me.readyState = 4;
  717. Ext.apply(me, me.simlet.exec(me));
  718. callback = me.jsonpCallback;
  719. if (callback) {
  720. text = callback + '(' + me.responseText + ')';
  721. eval(text);
  722. }
  723. },
  724. onTick: function() {
  725. var me = this;
  726. me.timer = null;
  727. me.onComplete();
  728. if (me.onreadystatechange) {
  729. me.onreadystatechange();
  730. }
  731. }
  732. });
  733. /**
  734. * This singleton manages simulated Ajax responses. This allows application logic to be
  735. * written unaware that its Ajax calls are being handled by simulations ("simlets"). This
  736. * is currently done by hooking {@link Ext.data.Connection} methods, so all users of that
  737. * class (and {@link Ext.Ajax} since it is a derived class) qualify for simulation.
  738. *
  739. * The requires hooks are inserted when either the {@link #init} method is called or the
  740. * first {@link Ext.ux.ajax.Simlet} is registered. For example:
  741. *
  742. * Ext.onReady(function () {
  743. * initAjaxSim();
  744. *
  745. * // normal stuff
  746. * });
  747. *
  748. * function initAjaxSim () {
  749. * Ext.ux.ajax.SimManager.init({
  750. * delay: 300
  751. * }).register({
  752. * '/app/data/url': {
  753. * type: 'json', // use JsonSimlet (type is like xtype for components)
  754. * data: [
  755. * { foo: 42, bar: 'abc' },
  756. * ...
  757. * ]
  758. * }
  759. * });
  760. * }
  761. *
  762. * As many URL's as desired can be registered and associated with a {@link Ext.ux.ajax.Simlet}.
  763. * To make non-simulated Ajax requests once this singleton is initialized, add a `nosim:true` option
  764. * to the Ajax options:
  765. *
  766. * Ext.Ajax.request({
  767. * url: 'page.php',
  768. * nosim: true, // ignored by normal Ajax request
  769. * params: {
  770. * id: 1
  771. * },
  772. * success: function(response){
  773. * var text = response.responseText;
  774. * // process server response here
  775. * }
  776. * });
  777. */
  778. Ext.define('Ext.ux.ajax.SimManager', {
  779. singleton: true,
  780. requires: [
  781. 'Ext.data.Connection',
  782. 'Ext.ux.ajax.SimXhr',
  783. 'Ext.ux.ajax.Simlet',
  784. 'Ext.ux.ajax.JsonSimlet'
  785. ],
  786. /**
  787. * @cfg {Ext.ux.ajax.Simlet} defaultSimlet
  788. * The {@link Ext.ux.ajax.Simlet} instance to use for non-matching URL's. By default, this will
  789. * return 404. Set this to null to use real Ajax calls for non-matching URL's.
  790. */
  791. /**
  792. * @cfg {String} defaultType
  793. * The default `type` to apply to generic {@link Ext.ux.ajax.Simlet} configuration objects. The
  794. * default is 'basic'.
  795. */
  796. defaultType: 'basic',
  797. /**
  798. * @cfg {Number} delay
  799. * The number of milliseconds to delay before delivering a response to an async request.
  800. */
  801. delay: 150,
  802. /**
  803. * @property {Boolean} ready
  804. * True once this singleton has initialized and applied its Ajax hooks.
  805. * @private
  806. */
  807. ready: false,
  808. constructor: function() {
  809. this.simlets = [];
  810. },
  811. getSimlet: function(url) {
  812. // Strip down to base URL (no query parameters or hash):
  813. var me = this,
  814. index = url.indexOf('?'),
  815. simlets = me.simlets,
  816. len = simlets.length,
  817. i, simlet, simUrl, match;
  818. if (index < 0) {
  819. index = url.indexOf('#');
  820. }
  821. if (index > 0) {
  822. url = url.substring(0, index);
  823. }
  824. for (i = 0; i < len; ++i) {
  825. simlet = simlets[i];
  826. simUrl = simlet.url;
  827. if (simUrl instanceof RegExp) {
  828. match = simUrl.test(url);
  829. } else {
  830. match = simUrl === url;
  831. }
  832. if (match) {
  833. return simlet;
  834. }
  835. }
  836. return me.defaultSimlet;
  837. },
  838. getXhr: function(method, url, options, async) {
  839. var simlet = this.getSimlet(url);
  840. if (simlet) {
  841. return simlet.openRequest(method, url, options, async);
  842. }
  843. return null;
  844. },
  845. /**
  846. * Initializes this singleton and applies configuration options.
  847. * @param {Object} config An optional object with configuration properties to apply.
  848. * @return {Ext.ux.ajax.SimManager} this
  849. */
  850. init: function(config) {
  851. var me = this;
  852. Ext.apply(me, config);
  853. if (!me.ready) {
  854. me.ready = true;
  855. if (!('defaultSimlet' in me)) {
  856. me.defaultSimlet = new Ext.ux.ajax.Simlet({
  857. status: 404,
  858. statusText: 'Not Found'
  859. });
  860. }
  861. me._openRequest = Ext.data.Connection.prototype.openRequest;
  862. Ext.data.request.Ajax.override({
  863. openRequest: function(options, requestOptions, async) {
  864. var xhr = !options.nosim && me.getXhr(requestOptions.method, requestOptions.url, options, async);
  865. if (!xhr) {
  866. xhr = this.callParent(arguments);
  867. }
  868. return xhr;
  869. }
  870. });
  871. if (Ext.data.JsonP) {
  872. Ext.data.JsonP.self.override({
  873. createScript: function(url, params, options) {
  874. var fullUrl = Ext.urlAppend(url, Ext.Object.toQueryString(params)),
  875. script = !options.nosim && me.getXhr('GET', fullUrl, options, true);
  876. if (!script) {
  877. script = this.callParent(arguments);
  878. }
  879. return script;
  880. },
  881. loadScript: function(request) {
  882. var script = request.script;
  883. if (script.simlet) {
  884. script.jsonpCallback = request.params[request.callbackKey];
  885. script.send(null);
  886. // Ext.data.JsonP will attempt dom removal of a script tag,
  887. // so emulate its presence
  888. request.script = document.createElement('script');
  889. } else {
  890. this.callParent(arguments);
  891. }
  892. }
  893. });
  894. }
  895. }
  896. return me;
  897. },
  898. openRequest: function(method, url, async) {
  899. var opt = {
  900. method: method,
  901. url: url
  902. };
  903. return this._openRequest.call(Ext.data.Connection.prototype, {}, opt, async);
  904. },
  905. /**
  906. * Registeres one or more {@link Ext.ux.ajax.Simlet} instances.
  907. * @param {Array/Object} simlet Either a {@link Ext.ux.ajax.Simlet} instance or config, an Array
  908. * of such elements or an Object keyed by URL with values that are {@link Ext.ux.ajax.Simlet}
  909. * instances or configs.
  910. */
  911. register: function(simlet) {
  912. var me = this;
  913. me.init();
  914. function reg(one) {
  915. var simlet = one;
  916. if (!simlet.isSimlet) {
  917. simlet = Ext.create('simlet.' + (simlet.type || simlet.stype || me.defaultType), one);
  918. }
  919. me.simlets.push(simlet);
  920. simlet.manager = me;
  921. }
  922. if (Ext.isArray(simlet)) {
  923. Ext.each(simlet, reg);
  924. } else if (simlet.isSimlet || simlet.url) {
  925. reg(simlet);
  926. } else {
  927. Ext.Object.each(simlet, function(url, s) {
  928. s.url = url;
  929. reg(s);
  930. });
  931. }
  932. return me;
  933. }
  934. });
  935. /**
  936. * This class simulates XML-based requests.
  937. */
  938. Ext.define('Ext.ux.ajax.XmlSimlet', {
  939. extend: 'Ext.ux.ajax.DataSimlet',
  940. alias: 'simlet.xml',
  941. /* eslint-disable indent */
  942. /**
  943. * This template is used to populate the XML response. The configuration of the Reader
  944. * is available so that its `root` and `record` properties can be used as well as the
  945. * `fields` of the associated `model`. But beyond that, the way these pieces are put
  946. * together in the document requires the flexibility of a template.
  947. */
  948. xmlTpl: [
  949. '<{root}>\n',
  950. '<tpl for="data">',
  951. ' <{parent.record}>\n',
  952. '<tpl for="parent.fields">',
  953. ' <{name}>{[parent[values.name]]}</{name}>\n',
  954. '</tpl>',
  955. ' </{parent.record}>\n',
  956. '</tpl>',
  957. '</{root}>'
  958. ],
  959. /* eslint-enable indent */
  960. doGet: function(ctx) {
  961. var me = this,
  962. data = me.getData(ctx),
  963. page = me.getPage(ctx, data),
  964. proxy = ctx.xhr.options.operation.getProxy(),
  965. reader = proxy && proxy.getReader(),
  966. model = reader && reader.getModel(),
  967. ret = me.callParent(arguments),
  968. // pick up status/statusText
  969. response = {
  970. data: page,
  971. reader: reader,
  972. fields: model && model.fields,
  973. root: reader && reader.getRootProperty(),
  974. record: reader && reader.record
  975. },
  976. tpl, xml, doc;
  977. if (ctx.groupSpec) {
  978. response.summaryData = me.getSummary(ctx, data, page);
  979. }
  980. // If a straight Ajax request there won't be an xmlTpl.
  981. if (me.xmlTpl) {
  982. tpl = Ext.XTemplate.getTpl(me, 'xmlTpl');
  983. xml = tpl.apply(response);
  984. } else {
  985. xml = data;
  986. }
  987. if (typeof DOMParser !== 'undefined') {
  988. doc = (new DOMParser()).parseFromString(xml, "text/xml");
  989. } else {
  990. /* global ActiveXObject */
  991. // IE doesn't have DOMParser, but fortunately, there is an ActiveX for XML
  992. doc = new ActiveXObject("Microsoft.XMLDOM");
  993. doc.async = false;
  994. doc.loadXML(xml);
  995. }
  996. ret.responseText = xml;
  997. ret.responseXML = doc;
  998. return ret;
  999. },
  1000. fixTree: function() {
  1001. var buffer = [];
  1002. this.callParent(arguments);
  1003. this.buildTreeXml(this.data, buffer);
  1004. this.data = buffer.join('');
  1005. },
  1006. buildTreeXml: function(nodes, buffer) {
  1007. var rootProperty = this.rootProperty,
  1008. recordProperty = this.recordProperty;
  1009. buffer.push('<', rootProperty, '>');
  1010. Ext.Array.forEach(nodes, function(node) {
  1011. var key;
  1012. buffer.push('<', recordProperty, '>');
  1013. for (key in node) {
  1014. if (key === 'children') {
  1015. this.buildTreeXml(node.children, buffer);
  1016. } else {
  1017. buffer.push('<', key, '>', node[key], '</', key, '>');
  1018. }
  1019. }
  1020. buffer.push('</', recordProperty, '>');
  1021. });
  1022. buffer.push('</', rootProperty, '>');
  1023. }
  1024. });
  1025. /**
  1026. * This is the base class for {@link Ext.ux.event.Recorder} and {@link Ext.ux.event.Player}.
  1027. */
  1028. Ext.define('Ext.ux.event.Driver', {
  1029. extend: 'Ext.util.Observable',
  1030. active: null,
  1031. specialKeysByName: {
  1032. PGUP: 33,
  1033. PGDN: 34,
  1034. END: 35,
  1035. HOME: 36,
  1036. LEFT: 37,
  1037. UP: 38,
  1038. RIGHT: 39,
  1039. DOWN: 40
  1040. },
  1041. specialKeysByCode: {},
  1042. /**
  1043. * @event start
  1044. * Fires when this object is started.
  1045. * @param {Ext.ux.event.Driver} this
  1046. */
  1047. /**
  1048. * @event stop
  1049. * Fires when this object is stopped.
  1050. * @param {Ext.ux.event.Driver} this
  1051. */
  1052. getTextSelection: function(el) {
  1053. // See https://code.google.com/p/rangyinputs/source/browse/trunk/rangyinputs_jquery.js
  1054. var doc = el.ownerDocument,
  1055. range, range2, start, end;
  1056. if (typeof el.selectionStart === "number") {
  1057. start = el.selectionStart;
  1058. end = el.selectionEnd;
  1059. } else if (doc.selection) {
  1060. range = doc.selection.createRange();
  1061. range2 = el.createTextRange();
  1062. range2.setEndPoint('EndToStart', range);
  1063. start = range2.text.length;
  1064. end = start + range.text.length;
  1065. }
  1066. return [
  1067. start,
  1068. end
  1069. ];
  1070. },
  1071. getTime: function() {
  1072. return new Date().getTime();
  1073. },
  1074. /**
  1075. * Returns the number of milliseconds since start was called.
  1076. */
  1077. getTimestamp: function() {
  1078. var d = this.getTime();
  1079. return d - this.startTime;
  1080. },
  1081. onStart: function() {},
  1082. onStop: function() {},
  1083. /**
  1084. * Starts this object. If this object is already started, nothing happens.
  1085. */
  1086. start: function() {
  1087. var me = this;
  1088. if (!me.active) {
  1089. me.active = new Date();
  1090. me.startTime = me.getTime();
  1091. me.onStart();
  1092. me.fireEvent('start', me);
  1093. }
  1094. },
  1095. /**
  1096. * Stops this object. If this object is not started, nothing happens.
  1097. */
  1098. stop: function() {
  1099. var me = this;
  1100. if (me.active) {
  1101. me.active = null;
  1102. me.onStop();
  1103. me.fireEvent('stop', me);
  1104. }
  1105. }
  1106. }, function() {
  1107. var proto = this.prototype;
  1108. Ext.Object.each(proto.specialKeysByName, function(name, value) {
  1109. proto.specialKeysByCode[value] = name;
  1110. });
  1111. });
  1112. /**
  1113. * Event maker.
  1114. */
  1115. Ext.define('Ext.ux.event.Maker', {
  1116. eventQueue: [],
  1117. startAfter: 500,
  1118. timerIncrement: 500,
  1119. currentTiming: 0,
  1120. constructor: function(config) {
  1121. var me = this;
  1122. me.currentTiming = me.startAfter;
  1123. if (!Ext.isArray(config)) {
  1124. config = [
  1125. config
  1126. ];
  1127. }
  1128. Ext.Array.each(config, function(item) {
  1129. item.el = item.el || 'el';
  1130. Ext.Array.each(Ext.ComponentQuery.query(item.cmpQuery), function(cmp) {
  1131. var event = {},
  1132. x, y, el;
  1133. if (!item.domQuery) {
  1134. el = cmp[item.el];
  1135. } else {
  1136. el = cmp.el.down(item.domQuery);
  1137. }
  1138. event.target = '#' + el.dom.id;
  1139. event.type = item.type;
  1140. event.button = config.button || 0;
  1141. x = el.getX() + (el.getWidth() / 2);
  1142. y = el.getY() + (el.getHeight() / 2);
  1143. event.xy = [
  1144. x,
  1145. y
  1146. ];
  1147. event.ts = me.currentTiming;
  1148. me.currentTiming += me.timerIncrement;
  1149. me.eventQueue.push(event);
  1150. });
  1151. if (item.screenshot) {
  1152. me.eventQueue[me.eventQueue.length - 1].screenshot = true;
  1153. }
  1154. });
  1155. return me.eventQueue;
  1156. }
  1157. });
  1158. /**
  1159. * @extends Ext.ux.event.Driver
  1160. * This class manages the playback of an array of "event descriptors". For details on the
  1161. * contents of an "event descriptor", see {@link Ext.ux.event.Recorder}. The events recorded by the
  1162. * {@link Ext.ux.event.Recorder} class are designed to serve as input for this class.
  1163. *
  1164. * The simplest use of this class is to instantiate it with an {@link #eventQueue} and call
  1165. * {@link #method-start}. Like so:
  1166. *
  1167. * var player = Ext.create('Ext.ux.event.Player', {
  1168. * eventQueue: [ ... ],
  1169. * speed: 2, // play at 2x speed
  1170. * listeners: {
  1171. * stop: function() {
  1172. * player = null; // all done
  1173. * }
  1174. * }
  1175. * });
  1176. *
  1177. * player.start();
  1178. *
  1179. * A more complex use would be to incorporate keyframe generation after playing certain
  1180. * events.
  1181. *
  1182. * var player = Ext.create('Ext.ux.event.Player', {
  1183. * eventQueue: [ ... ],
  1184. * keyFrameEvents: {
  1185. * click: true
  1186. * },
  1187. * listeners: {
  1188. * stop: function() {
  1189. * // play has completed... probably time for another keyframe...
  1190. * player = null;
  1191. * },
  1192. * keyframe: onKeyFrame
  1193. * }
  1194. * });
  1195. *
  1196. * player.start();
  1197. *
  1198. * If a keyframe can be handled immediately (synchronously), the listener would be:
  1199. *
  1200. * function onKeyFrame () {
  1201. * handleKeyFrame();
  1202. * }
  1203. *
  1204. * If the keyframe event is always handled asynchronously, then the event listener is only
  1205. * a bit more:
  1206. *
  1207. * function onKeyFrame (p, eventDescriptor) {
  1208. * eventDescriptor.defer(); // pause event playback...
  1209. *
  1210. * handleKeyFrame(function() {
  1211. * eventDescriptor.finish(); // ...resume event playback
  1212. * });
  1213. * }
  1214. *
  1215. * Finally, if the keyframe could be either handled synchronously or asynchronously (perhaps
  1216. * differently by browser), a slightly more complex listener is required.
  1217. *
  1218. * function onKeyFrame (p, eventDescriptor) {
  1219. * var async;
  1220. *
  1221. * handleKeyFrame(function() {
  1222. * // either this callback is being called immediately by handleKeyFrame (in
  1223. * // which case async is undefined) or it is being called later (in which case
  1224. * // async will be true).
  1225. *
  1226. * if (async) {
  1227. * eventDescriptor.finish();
  1228. * }
  1229. * else {
  1230. * async = false;
  1231. * }
  1232. * });
  1233. *
  1234. * // either the callback was called (and async is now false) or it was not
  1235. * // called (and async remains undefined).
  1236. *
  1237. * if (async !== false) {
  1238. * eventDescriptor.defer();
  1239. * async = true; // let the callback know that we have gone async
  1240. * }
  1241. * }
  1242. */
  1243. Ext.define('Ext.ux.event.Player', function(Player) {
  1244. /* eslint-disable indent, vars-on-top, one-var */
  1245. var defaults = {},
  1246. mouseEvents = {},
  1247. keyEvents = {},
  1248. doc,
  1249. // HTML events supported
  1250. uiEvents = {},
  1251. // events that bubble by default
  1252. bubbleEvents = {
  1253. // scroll: 1,
  1254. resize: 1,
  1255. reset: 1,
  1256. submit: 1,
  1257. change: 1,
  1258. select: 1,
  1259. error: 1,
  1260. abort: 1
  1261. };
  1262. Ext.each([
  1263. 'click',
  1264. 'dblclick',
  1265. 'mouseover',
  1266. 'mouseout',
  1267. 'mousedown',
  1268. 'mouseup',
  1269. 'mousemove'
  1270. ], function(type) {
  1271. bubbleEvents[type] = defaults[type] = mouseEvents[type] = {
  1272. bubbles: true,
  1273. cancelable: (type !== "mousemove"),
  1274. // mousemove cannot be cancelled
  1275. detail: 1,
  1276. screenX: 0,
  1277. screenY: 0,
  1278. clientX: 0,
  1279. clientY: 0,
  1280. ctrlKey: false,
  1281. altKey: false,
  1282. shiftKey: false,
  1283. metaKey: false,
  1284. button: 0
  1285. };
  1286. });
  1287. Ext.each([
  1288. 'keydown',
  1289. 'keyup',
  1290. 'keypress'
  1291. ], function(type) {
  1292. bubbleEvents[type] = defaults[type] = keyEvents[type] = {
  1293. bubbles: true,
  1294. cancelable: true,
  1295. ctrlKey: false,
  1296. altKey: false,
  1297. shiftKey: false,
  1298. metaKey: false,
  1299. keyCode: 0,
  1300. charCode: 0
  1301. };
  1302. });
  1303. Ext.each([
  1304. 'blur',
  1305. 'change',
  1306. 'focus',
  1307. 'resize',
  1308. 'scroll',
  1309. 'select'
  1310. ], function(type) {
  1311. defaults[type] = uiEvents[type] = {
  1312. bubbles: (type in bubbleEvents),
  1313. cancelable: false,
  1314. detail: 1
  1315. };
  1316. });
  1317. var inputSpecialKeys = {
  1318. 8: function(target, start, end) {
  1319. // backspace: 8,
  1320. if (start < end) {
  1321. target.value = target.value.substring(0, start) + target.value.substring(end);
  1322. } else if (start > 0) {
  1323. target.value = target.value.substring(0, --start) + target.value.substring(end);
  1324. }
  1325. this.setTextSelection(target, start, start);
  1326. },
  1327. 46: function(target, start, end) {
  1328. // delete: 46
  1329. if (start < end) {
  1330. target.value = target.value.substring(0, start) + target.value.substring(end);
  1331. } else if (start < target.value.length - 1) {
  1332. target.value = target.value.substring(0, start) + target.value.substring(start + 1);
  1333. }
  1334. this.setTextSelection(target, start, start);
  1335. }
  1336. };
  1337. return {
  1338. extend: 'Ext.ux.event.Driver',
  1339. /**
  1340. * @cfg {Array} eventQueue The event queue to playback. This must be provided before
  1341. * the {@link #method-start} method is called.
  1342. */
  1343. /**
  1344. * @cfg {Object} keyFrameEvents An object that describes the events that should generate
  1345. * keyframe events. For example, `{ click: true }` would generate keyframe events after
  1346. * each `click` event.
  1347. */
  1348. keyFrameEvents: {
  1349. click: true
  1350. },
  1351. /**
  1352. * @cfg {Boolean} pauseForAnimations True to pause event playback during animations, false
  1353. * to ignore animations. Default is true.
  1354. */
  1355. pauseForAnimations: true,
  1356. /**
  1357. * @cfg {Number} speed The playback speed multiplier. Default is 1.0 (to playback at the
  1358. * recorded speed). A value of 2 would playback at 2x speed.
  1359. */
  1360. speed: 1,
  1361. stallTime: 0,
  1362. _inputSpecialKeys: {
  1363. INPUT: inputSpecialKeys,
  1364. TEXTAREA: Ext.apply({}, // 13: function(target, start, end) { // enter: 8,
  1365. // TODO ?
  1366. // }
  1367. inputSpecialKeys)
  1368. },
  1369. tagPathRegEx: /(\w+)(?:\[(\d+)\])?/,
  1370. /**
  1371. * @event beforeplay
  1372. * Fires before an event is played.
  1373. * @param {Ext.ux.event.Player} this
  1374. * @param {Object} eventDescriptor The event descriptor about to be played.
  1375. */
  1376. /**
  1377. * @event keyframe
  1378. * Fires when this player reaches a keyframe. Typically, this is after events
  1379. * like `click` are injected and any resulting animations have been completed.
  1380. * @param {Ext.ux.event.Player} this
  1381. * @param {Object} eventDescriptor The keyframe event descriptor.
  1382. */
  1383. constructor: function(config) {
  1384. var me = this;
  1385. me.callParent(arguments);
  1386. me.timerFn = function() {
  1387. me.onTick();
  1388. };
  1389. me.attachTo = me.attachTo || window;
  1390. doc = me.attachTo.document;
  1391. },
  1392. /**
  1393. * Returns the element given is XPath-like description.
  1394. * @param {String} xpath The XPath-like description of the element.
  1395. * @return {HTMLElement}
  1396. */
  1397. getElementFromXPath: function(xpath) {
  1398. var me = this,
  1399. parts = xpath.split('/'),
  1400. regex = me.tagPathRegEx,
  1401. i, n, m, count, tag, child,
  1402. el = me.attachTo.document;
  1403. el = (parts[0] === '~') ? el.body : el.getElementById(parts[0].substring(1));
  1404. // remove '#'
  1405. for (i = 1 , n = parts.length; el && i < n; ++i) {
  1406. m = regex.exec(parts[i]);
  1407. count = m[2] ? parseInt(m[2], 10) : 1;
  1408. tag = m[1].toUpperCase();
  1409. for (child = el.firstChild; child; child = child.nextSibling) {
  1410. if (child.tagName === tag) {
  1411. if (count === 1) {
  1412. break;
  1413. }
  1414. --count;
  1415. }
  1416. }
  1417. el = child;
  1418. }
  1419. return el;
  1420. },
  1421. // Moving across a line break only counts as moving one character in a TextRange, whereas
  1422. // a line break in the textarea value is two characters. This function corrects for that
  1423. // by converting a text offset into a range character offset by subtracting one character
  1424. // for every line break in the textarea prior to the offset
  1425. offsetToRangeCharacterMove: function(el, offset) {
  1426. return offset - (el.value.slice(0, offset).split("\r\n").length - 1);
  1427. },
  1428. setTextSelection: function(el, startOffset, endOffset) {
  1429. var range, startCharMove;
  1430. // See https://code.google.com/p/rangyinputs/source/browse/trunk/rangyinputs_jquery.js
  1431. if (startOffset < 0) {
  1432. startOffset += el.value.length;
  1433. }
  1434. if (endOffset == null) {
  1435. endOffset = startOffset;
  1436. }
  1437. if (endOffset < 0) {
  1438. endOffset += el.value.length;
  1439. }
  1440. if (typeof el.selectionStart === "number") {
  1441. el.selectionStart = startOffset;
  1442. el.selectionEnd = endOffset;
  1443. } else {
  1444. range = el.createTextRange();
  1445. startCharMove = this.offsetToRangeCharacterMove(el, startOffset);
  1446. range.collapse(true);
  1447. if (startOffset === endOffset) {
  1448. range.move("character", startCharMove);
  1449. } else {
  1450. range.moveEnd("character", this.offsetToRangeCharacterMove(el, endOffset));
  1451. range.moveStart("character", startCharMove);
  1452. }
  1453. range.select();
  1454. }
  1455. },
  1456. getTimeIndex: function() {
  1457. var t = this.getTimestamp() - this.stallTime;
  1458. return t * this.speed;
  1459. },
  1460. makeToken: function(eventDescriptor, signal) {
  1461. var me = this,
  1462. t0;
  1463. eventDescriptor[signal] = true;
  1464. eventDescriptor.defer = function() {
  1465. eventDescriptor[signal] = false;
  1466. t0 = me.getTime();
  1467. };
  1468. eventDescriptor.finish = function() {
  1469. eventDescriptor[signal] = true;
  1470. me.stallTime += me.getTime() - t0;
  1471. me.schedule();
  1472. };
  1473. },
  1474. /**
  1475. * This method is called after an event has been played to prepare for the next event.
  1476. * @param {Object} eventDescriptor The descriptor of the event just played.
  1477. */
  1478. nextEvent: function(eventDescriptor) {
  1479. var me = this,
  1480. index = ++me.queueIndex;
  1481. // keyframe events are inserted after a keyFrameEvent is played.
  1482. if (me.keyFrameEvents[eventDescriptor.type]) {
  1483. Ext.Array.insert(me.eventQueue, index, [
  1484. {
  1485. keyframe: true,
  1486. ts: eventDescriptor.ts
  1487. }
  1488. ]);
  1489. }
  1490. },
  1491. /**
  1492. * This method returns the event descriptor at the front of the queue. This does not
  1493. * dequeue the event. Repeated calls return the same object (until {@link #nextEvent}
  1494. * is called).
  1495. */
  1496. peekEvent: function() {
  1497. return this.eventQueue[this.queueIndex] || null;
  1498. },
  1499. /**
  1500. * Replaces an event in the queue with an array of events. This is often used to roll
  1501. * up a multi-step pseudo-event and expand it just-in-time to be played. The process
  1502. * for doing this in a derived class would be this:
  1503. *
  1504. * Ext.define('My.Player', {
  1505. * extend: 'Ext.ux.event.Player',
  1506. *
  1507. * peekEvent: function() {
  1508. * var event = this.callParent();
  1509. *
  1510. * if (event.multiStepSpecial) {
  1511. * this.replaceEvent(null, [
  1512. * ... expand to actual events
  1513. * ]);
  1514. *
  1515. * event = this.callParent(); // get the new next event
  1516. * }
  1517. *
  1518. * return event;
  1519. * }
  1520. * });
  1521. *
  1522. * This method ensures that the `beforeplay` hook (if any) from the replaced event is
  1523. * placed on the first new event and the `afterplay` hook (if any) is placed on the
  1524. * last new event.
  1525. *
  1526. * @param {Number} index The queue index to replace. Pass `null` to replace the event
  1527. * at the current `queueIndex`.
  1528. * @param {Event[]} events The array of events with which to replace the specified
  1529. * event.
  1530. */
  1531. replaceEvent: function(index, events) {
  1532. for (var t,
  1533. i = 0,
  1534. n = events.length; i < n; ++i) {
  1535. if (i) {
  1536. t = events[i - 1];
  1537. delete t.afterplay;
  1538. delete t.screenshot;
  1539. delete events[i].beforeplay;
  1540. }
  1541. }
  1542. Ext.Array.replace(this.eventQueue, (index == null) ? this.queueIndex : index, 1, events);
  1543. },
  1544. /**
  1545. * This method dequeues and injects events until it has arrived at the time index. If
  1546. * no events are ready (based on the time index), this method does nothing.
  1547. * @return {Boolean} True if there is more to do; false if not (at least for now).
  1548. */
  1549. processEvents: function() {
  1550. var me = this,
  1551. animations = me.pauseForAnimations && me.attachTo.Ext.fx.Manager.items,
  1552. eventDescriptor;
  1553. while ((eventDescriptor = me.peekEvent()) !== null) {
  1554. if (animations && animations.getCount()) {
  1555. return true;
  1556. }
  1557. if (eventDescriptor.keyframe) {
  1558. if (!me.processKeyFrame(eventDescriptor)) {
  1559. return false;
  1560. }
  1561. me.nextEvent(eventDescriptor);
  1562. } else if (eventDescriptor.ts <= me.getTimeIndex() && me.fireEvent('beforeplay', me, eventDescriptor) !== false && me.playEvent(eventDescriptor)) {
  1563. me.nextEvent(eventDescriptor);
  1564. } else {
  1565. return true;
  1566. }
  1567. }
  1568. me.stop();
  1569. return false;
  1570. },
  1571. /**
  1572. * This method is called when a keyframe is reached. This will fire the keyframe event.
  1573. * If the keyframe has been handled, true is returned. Otherwise, false is returned.
  1574. * @param {Object} eventDescriptor The event descriptor of the keyframe.
  1575. * @return {Boolean} True if the keyframe was handled, false if not.
  1576. */
  1577. processKeyFrame: function(eventDescriptor) {
  1578. var me = this;
  1579. // only fire keyframe event (and setup the eventDescriptor) once...
  1580. if (!eventDescriptor.defer) {
  1581. me.makeToken(eventDescriptor, 'done');
  1582. me.fireEvent('keyframe', me, eventDescriptor);
  1583. }
  1584. return eventDescriptor.done;
  1585. },
  1586. /**
  1587. * Called to inject the given event on the specified target.
  1588. * @param {HTMLElement} target The target of the event.
  1589. * @param {Object} event The event to inject. The properties of this object should be
  1590. * those of standard DOM events but vary based on the `type` property. For details on
  1591. * event types and their properties, see the class documentation.
  1592. */
  1593. injectEvent: function(target, event) {
  1594. var me = this,
  1595. type = event.type,
  1596. options = Ext.apply({}, event, defaults[type]),
  1597. handler;
  1598. if (type === 'type') {
  1599. handler = me._inputSpecialKeys[target.tagName];
  1600. if (handler) {
  1601. return me.injectTypeInputEvent(target, event, handler);
  1602. }
  1603. return me.injectTypeEvent(target, event);
  1604. }
  1605. if (type === 'focus' && target.focus) {
  1606. target.focus();
  1607. return true;
  1608. }
  1609. if (type === 'blur' && target.blur) {
  1610. target.blur();
  1611. return true;
  1612. }
  1613. if (type === 'scroll') {
  1614. target.scrollLeft = event.pos[0];
  1615. target.scrollTop = event.pos[1];
  1616. return true;
  1617. }
  1618. if (type === 'mduclick') {
  1619. return me.injectEvent(target, Ext.applyIf({
  1620. type: 'mousedown'
  1621. }, event)) && me.injectEvent(target, Ext.applyIf({
  1622. type: 'mouseup'
  1623. }, event)) && me.injectEvent(target, Ext.applyIf({
  1624. type: 'click'
  1625. }, event));
  1626. }
  1627. if (mouseEvents[type]) {
  1628. return Player.injectMouseEvent(target, options, me.attachTo);
  1629. }
  1630. if (keyEvents[type]) {
  1631. return Player.injectKeyEvent(target, options, me.attachTo);
  1632. }
  1633. if (uiEvents[type]) {
  1634. return Player.injectUIEvent(target, type, options.bubbles, options.cancelable, options.view || me.attachTo, options.detail);
  1635. }
  1636. return false;
  1637. },
  1638. injectTypeEvent: function(target, event) {
  1639. var me = this,
  1640. text = event.text,
  1641. xlat = [],
  1642. ch, chUp, i, n, upper;
  1643. if (text) {
  1644. delete event.text;
  1645. upper = text.toUpperCase();
  1646. for (i = 0 , n = text.length; i < n; ++i) {
  1647. ch = text.charCodeAt(i);
  1648. chUp = upper.charCodeAt(i);
  1649. xlat.push(Ext.applyIf({
  1650. type: 'keydown',
  1651. charCode: chUp,
  1652. keyCode: chUp
  1653. }, event), Ext.applyIf({
  1654. type: 'keypress',
  1655. charCode: ch,
  1656. keyCode: ch
  1657. }, event), Ext.applyIf({
  1658. type: 'keyup',
  1659. charCode: chUp,
  1660. keyCode: chUp
  1661. }, event));
  1662. }
  1663. } else {
  1664. xlat.push(Ext.applyIf({
  1665. type: 'keydown',
  1666. charCode: event.keyCode
  1667. }, event), Ext.applyIf({
  1668. type: 'keyup',
  1669. charCode: event.keyCode
  1670. }, event));
  1671. }
  1672. for (i = 0 , n = xlat.length; i < n; ++i) {
  1673. me.injectEvent(target, xlat[i]);
  1674. }
  1675. return true;
  1676. },
  1677. injectTypeInputEvent: function(target, event, handler) {
  1678. var me = this,
  1679. text = event.text,
  1680. sel, n;
  1681. if (handler) {
  1682. sel = me.getTextSelection(target);
  1683. if (text) {
  1684. n = sel[0];
  1685. target.value = target.value.substring(0, n) + text + target.value.substring(sel[1]);
  1686. n += text.length;
  1687. me.setTextSelection(target, n, n);
  1688. } else {
  1689. if (!(handler = handler[event.keyCode])) {
  1690. // no handler for the special key for this element
  1691. if ('caret' in event) {
  1692. me.setTextSelection(target, event.caret, event.caret);
  1693. } else if (event.selection) {
  1694. me.setTextSelection(target, event.selection[0], event.selection[1]);
  1695. }
  1696. return me.injectTypeEvent(target, event);
  1697. }
  1698. handler.call(this, target, sel[0], sel[1]);
  1699. return true;
  1700. }
  1701. }
  1702. return true;
  1703. },
  1704. playEvent: function(eventDescriptor) {
  1705. var me = this,
  1706. target = me.getElementFromXPath(eventDescriptor.target),
  1707. event;
  1708. if (!target) {
  1709. // not present (yet)... wait for element present...
  1710. // TODO - need a timeout here
  1711. return false;
  1712. }
  1713. if (!me.playEventHook(eventDescriptor, 'beforeplay')) {
  1714. return false;
  1715. }
  1716. if (!eventDescriptor.injected) {
  1717. eventDescriptor.injected = true;
  1718. event = me.translateEvent(eventDescriptor, target);
  1719. me.injectEvent(target, event);
  1720. }
  1721. return me.playEventHook(eventDescriptor, 'afterplay');
  1722. },
  1723. playEventHook: function(eventDescriptor, hookName) {
  1724. var me = this,
  1725. doneName = hookName + '.done',
  1726. firedName = hookName + '.fired',
  1727. hook = eventDescriptor[hookName];
  1728. if (hook && !eventDescriptor[doneName]) {
  1729. if (!eventDescriptor[firedName]) {
  1730. eventDescriptor[firedName] = true;
  1731. me.makeToken(eventDescriptor, doneName);
  1732. if (me.eventScope && Ext.isString(hook)) {
  1733. hook = me.eventScope[hook];
  1734. }
  1735. if (hook) {
  1736. hook.call(me.eventScope || me, eventDescriptor);
  1737. }
  1738. }
  1739. return false;
  1740. }
  1741. return true;
  1742. },
  1743. schedule: function() {
  1744. var me = this;
  1745. if (!me.timer) {
  1746. me.timer = Ext.defer(me.timerFn, 10);
  1747. }
  1748. },
  1749. _translateAcross: [
  1750. 'type',
  1751. 'button',
  1752. 'charCode',
  1753. 'keyCode',
  1754. 'caret',
  1755. 'pos',
  1756. 'text',
  1757. 'selection'
  1758. ],
  1759. translateEvent: function(eventDescriptor, target) {
  1760. var me = this,
  1761. event = {},
  1762. modKeys = eventDescriptor.modKeys || '',
  1763. names = me._translateAcross,
  1764. i = names.length,
  1765. name, xy;
  1766. while (i--) {
  1767. name = names[i];
  1768. if (name in eventDescriptor) {
  1769. event[name] = eventDescriptor[name];
  1770. }
  1771. }
  1772. event.altKey = modKeys.indexOf('A') > 0;
  1773. event.ctrlKey = modKeys.indexOf('C') > 0;
  1774. event.metaKey = modKeys.indexOf('M') > 0;
  1775. event.shiftKey = modKeys.indexOf('S') > 0;
  1776. if (target && 'x' in eventDescriptor) {
  1777. xy = Ext.fly(target).getXY();
  1778. xy[0] += eventDescriptor.x;
  1779. xy[1] += eventDescriptor.y;
  1780. } else if ('x' in eventDescriptor) {
  1781. xy = [
  1782. eventDescriptor.x,
  1783. eventDescriptor.y
  1784. ];
  1785. } else if ('px' in eventDescriptor) {
  1786. xy = [
  1787. eventDescriptor.px,
  1788. eventDescriptor.py
  1789. ];
  1790. }
  1791. if (xy) {
  1792. event.clientX = event.screenX = xy[0];
  1793. event.clientY = event.screenY = xy[1];
  1794. }
  1795. if (eventDescriptor.key) {
  1796. event.keyCode = me.specialKeysByName[eventDescriptor.key];
  1797. }
  1798. if (eventDescriptor.type === 'wheel') {
  1799. if ('onwheel' in me.attachTo.document) {
  1800. event.wheelX = eventDescriptor.dx;
  1801. event.wheelY = eventDescriptor.dy;
  1802. } else {
  1803. event.type = 'mousewheel';
  1804. event.wheelDeltaX = -40 * eventDescriptor.dx;
  1805. event.wheelDeltaY = event.wheelDelta = -40 * eventDescriptor.dy;
  1806. }
  1807. }
  1808. return event;
  1809. },
  1810. //---------------------------------
  1811. // Driver overrides
  1812. onStart: function() {
  1813. var me = this;
  1814. me.queueIndex = 0;
  1815. me.schedule();
  1816. },
  1817. onStop: function() {
  1818. var me = this;
  1819. if (me.timer) {
  1820. Ext.undefer(me.timer);
  1821. me.timer = null;
  1822. }
  1823. },
  1824. //---------------------------------
  1825. onTick: function() {
  1826. var me = this;
  1827. me.timer = null;
  1828. if (me.processEvents()) {
  1829. me.schedule();
  1830. }
  1831. },
  1832. statics: {
  1833. ieButtonCodeMap: {
  1834. 0: 1,
  1835. 1: 4,
  1836. 2: 2
  1837. },
  1838. /**
  1839. * Injects a key event using the given event information to populate the event
  1840. * object.
  1841. *
  1842. * **Note:** `keydown` causes Safari 2.x to crash.
  1843. *
  1844. * @param {HTMLElement} target The target of the given event.
  1845. * @param {Object} options Object object containing all of the event injection
  1846. * options.
  1847. * @param {String} options.type The type of event to fire. This can be any one of
  1848. * the following: `keyup`, `keydown` and `keypress`.
  1849. * @param {Boolean} [options.bubbles=true] `tru` if the event can be bubbled up.
  1850. * DOM Level 3 specifies that all key events bubble by default.
  1851. * @param {Boolean} [options.cancelable=true] `true` if the event can be canceled
  1852. * using `preventDefault`. DOM Level 3 specifies that all key events can be
  1853. * cancelled.
  1854. * @param {Boolean} [options.ctrlKey=false] `true` if one of the CTRL keys is
  1855. * pressed while the event is firing.
  1856. * @param {Boolean} [options.altKey=false] `true` if one of the ALT keys is
  1857. * pressed while the event is firing.
  1858. * @param {Boolean} [options.shiftKey=false] `true` if one of the SHIFT keys is
  1859. * pressed while the event is firing.
  1860. * @param {Boolean} [options.metaKey=false] `true` if one of the META keys is
  1861. * pressed while the event is firing.
  1862. * @param {Number} [options.keyCode=0] The code for the key that is in use.
  1863. * @param {Number} [options.charCode=0] The Unicode code for the character
  1864. * associated with the key being used.
  1865. * @param {Window} [view=window] The view containing the target. This is typically
  1866. * the window object.
  1867. * @private
  1868. */
  1869. injectKeyEvent: function(target, options, view) {
  1870. var type = options.type,
  1871. customEvent = null;
  1872. if (type === 'textevent') {
  1873. type = 'keypress';
  1874. }
  1875. view = view || window;
  1876. // check for DOM-compliant browsers first
  1877. if (doc.createEvent) {
  1878. try {
  1879. customEvent = doc.createEvent("KeyEvents");
  1880. // Interesting problem: Firefox implemented a non-standard
  1881. // version of initKeyEvent() based on DOM Level 2 specs.
  1882. // Key event was removed from DOM Level 2 and re-introduced
  1883. // in DOM Level 3 with a different interface. Firefox is the
  1884. // only browser with any implementation of Key Events, so for
  1885. // now, assume it's Firefox if the above line doesn't error.
  1886. // @TODO: Decipher between Firefox's implementation and a correct one.
  1887. customEvent.initKeyEvent(type, options.bubbles, options.cancelable, view, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.keyCode, options.charCode);
  1888. } catch (ex) {
  1889. // If it got here, that means key events aren't officially supported.
  1890. // Safari/WebKit is a real problem now. WebKit 522 won't let you
  1891. // set keyCode, charCode, or other properties if you use a
  1892. // UIEvent, so we first must try to create a generic event. The
  1893. // fun part is that this will throw an error on Safari 2.x. The
  1894. // end result is that we need another try...catch statement just to
  1895. // deal with this mess.
  1896. try {
  1897. // try to create generic event - will fail in Safari 2.x
  1898. customEvent = doc.createEvent("Events");
  1899. } catch (uierror) {
  1900. // the above failed, so create a UIEvent for Safari 2.x
  1901. customEvent = doc.createEvent("UIEvents");
  1902. } finally {
  1903. customEvent.initEvent(type, options.bubbles, options.cancelable);
  1904. customEvent.view = view;
  1905. customEvent.altKey = options.altKey;
  1906. customEvent.ctrlKey = options.ctrlKey;
  1907. customEvent.shiftKey = options.shiftKey;
  1908. customEvent.metaKey = options.metaKey;
  1909. customEvent.keyCode = options.keyCode;
  1910. customEvent.charCode = options.charCode;
  1911. }
  1912. }
  1913. target.dispatchEvent(customEvent);
  1914. } else if (doc.createEventObject) {
  1915. // IE
  1916. customEvent = doc.createEventObject();
  1917. customEvent.bubbles = options.bubbles;
  1918. customEvent.cancelable = options.cancelable;
  1919. customEvent.view = view;
  1920. customEvent.ctrlKey = options.ctrlKey;
  1921. customEvent.altKey = options.altKey;
  1922. customEvent.shiftKey = options.shiftKey;
  1923. customEvent.metaKey = options.metaKey;
  1924. // IE doesn't support charCode explicitly. CharCode should
  1925. // take precedence over any keyCode value for accurate
  1926. // representation.
  1927. customEvent.keyCode = (options.charCode > 0) ? options.charCode : options.keyCode;
  1928. target.fireEvent("on" + type, customEvent);
  1929. } else {
  1930. return false;
  1931. }
  1932. return true;
  1933. },
  1934. /**
  1935. * Injects a mouse event using the given event information to populate the event
  1936. * object.
  1937. *
  1938. * @param {HTMLElement} target The target of the given event.
  1939. * @param {Object} options Object object containing all of the event injection
  1940. * options.
  1941. * @param {String} options.type The type of event to fire. This can be any one of
  1942. * the following: `click`, `dblclick`, `mousedown`, `mouseup`, `mouseout`,
  1943. * `mouseover` and `mousemove`.
  1944. * @param {Boolean} [options.bubbles=true] `tru` if the event can be bubbled up.
  1945. * DOM Level 2 specifies that all mouse events bubble by default.
  1946. * @param {Boolean} [options.cancelable=true] `true` if the event can be canceled
  1947. * using `preventDefault`. DOM Level 2 specifies that all mouse events except
  1948. * `mousemove` can be cancelled. This defaults to `false` for `mousemove`.
  1949. * @param {Boolean} [options.ctrlKey=false] `true` if one of the CTRL keys is
  1950. * pressed while the event is firing.
  1951. * @param {Boolean} [options.altKey=false] `true` if one of the ALT keys is
  1952. * pressed while the event is firing.
  1953. * @param {Boolean} [options.shiftKey=false] `true` if one of the SHIFT keys is
  1954. * pressed while the event is firing.
  1955. * @param {Boolean} [options.metaKey=false] `true` if one of the META keys is
  1956. * pressed while the event is firing.
  1957. * @param {Number} [options.detail=1] The number of times the mouse button has
  1958. * been used.
  1959. * @param {Number} [options.screenX=0] The x-coordinate on the screen at which point
  1960. * the event occurred.
  1961. * @param {Number} [options.screenY=0] The y-coordinate on the screen at which point
  1962. * the event occurred.
  1963. * @param {Number} [options.clientX=0] The x-coordinate on the client at which point
  1964. * the event occurred.
  1965. * @param {Number} [options.clientY=0] The y-coordinate on the client at which point
  1966. * the event occurred.
  1967. * @param {Number} [options.button=0] The button being pressed while the event is
  1968. * executing. The value should be 0 for the primary mouse button (typically the
  1969. * left button), 1 for the tertiary mouse button (typically the middle button),
  1970. * and 2 for the secondary mouse button (typically the right button).
  1971. * @param {HTMLElement} [options.relatedTarget=null] For `mouseout` events, this
  1972. * is the element that the mouse has moved to. For `mouseover` events, this is
  1973. * the element that the mouse has moved from. This argument is ignored for all
  1974. * other events.
  1975. * @param {Window} [view=window] The view containing the target. This is typically
  1976. * the window object.
  1977. * @private
  1978. */
  1979. injectMouseEvent: function(target, options, view) {
  1980. var type = options.type,
  1981. customEvent = null;
  1982. view = view || window;
  1983. // check for DOM-compliant browsers first
  1984. if (doc.createEvent) {
  1985. customEvent = doc.createEvent("MouseEvents");
  1986. // Safari 2.x (WebKit 418) still doesn't implement initMouseEvent()
  1987. if (customEvent.initMouseEvent) {
  1988. customEvent.initMouseEvent(type, options.bubbles, options.cancelable, view, options.detail, options.screenX, options.screenY, options.clientX, options.clientY, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, options.relatedTarget);
  1989. } else {
  1990. // Safari
  1991. // the closest thing available in Safari 2.x is UIEvents
  1992. customEvent = doc.createEvent("UIEvents");
  1993. customEvent.initEvent(type, options.bubbles, options.cancelable);
  1994. customEvent.view = view;
  1995. customEvent.detail = options.detail;
  1996. customEvent.screenX = options.screenX;
  1997. customEvent.screenY = options.screenY;
  1998. customEvent.clientX = options.clientX;
  1999. customEvent.clientY = options.clientY;
  2000. customEvent.ctrlKey = options.ctrlKey;
  2001. customEvent.altKey = options.altKey;
  2002. customEvent.metaKey = options.metaKey;
  2003. customEvent.shiftKey = options.shiftKey;
  2004. customEvent.button = options.button;
  2005. customEvent.relatedTarget = options.relatedTarget;
  2006. }
  2007. /*
  2008. * Check to see if relatedTarget has been assigned. Firefox
  2009. * versions less than 2.0 don't allow it to be assigned via
  2010. * initMouseEvent() and the property is readonly after event
  2011. * creation, so in order to keep YAHOO.util.getRelatedTarget()
  2012. * working, assign to the IE proprietary toElement property
  2013. * for mouseout event and fromElement property for mouseover
  2014. * event.
  2015. */
  2016. if (options.relatedTarget && !customEvent.relatedTarget) {
  2017. if (type === "mouseout") {
  2018. customEvent.toElement = options.relatedTarget;
  2019. } else if (type === "mouseover") {
  2020. customEvent.fromElement = options.relatedTarget;
  2021. }
  2022. }
  2023. target.dispatchEvent(customEvent);
  2024. } else if (doc.createEventObject) {
  2025. // IE
  2026. customEvent = doc.createEventObject();
  2027. customEvent.bubbles = options.bubbles;
  2028. customEvent.cancelable = options.cancelable;
  2029. customEvent.view = view;
  2030. customEvent.detail = options.detail;
  2031. customEvent.screenX = options.screenX;
  2032. customEvent.screenY = options.screenY;
  2033. customEvent.clientX = options.clientX;
  2034. customEvent.clientY = options.clientY;
  2035. customEvent.ctrlKey = options.ctrlKey;
  2036. customEvent.altKey = options.altKey;
  2037. customEvent.metaKey = options.metaKey;
  2038. customEvent.shiftKey = options.shiftKey;
  2039. customEvent.button = Player.ieButtonCodeMap[options.button] || 0;
  2040. /*
  2041. * Have to use relatedTarget because IE won't allow assignment
  2042. * to toElement or fromElement on generic events. This keeps
  2043. * YAHOO.util.customEvent.getRelatedTarget() functional.
  2044. */
  2045. customEvent.relatedTarget = options.relatedTarget;
  2046. target.fireEvent('on' + type, customEvent);
  2047. } else {
  2048. return false;
  2049. }
  2050. return true;
  2051. },
  2052. /**
  2053. * Injects a UI event using the given event information to populate the event
  2054. * object.
  2055. *
  2056. * @param {HTMLElement} target The target of the given event.
  2057. * @param {Object} options
  2058. * @param {String} options.type The type of event to fire. This can be any one of
  2059. * the following: `click`, `dblclick`, `mousedown`, `mouseup`, `mouseout`,
  2060. * `mouseover` and `mousemove`.
  2061. * @param {Boolean} [options.bubbles=true] `tru` if the event can be bubbled up.
  2062. * DOM Level 2 specifies that all mouse events bubble by default.
  2063. * @param {Boolean} [options.cancelable=true] `true` if the event can be canceled
  2064. * using `preventDefault`. DOM Level 2 specifies that all mouse events except
  2065. * `mousemove` can be canceled. This defaults to `false` for `mousemove`.
  2066. * @param {Number} [options.detail=1] The number of times the mouse button has been
  2067. * used.
  2068. * @param {Window} [view=window] The view containing the target. This is typically
  2069. * the window object.
  2070. * @private
  2071. */
  2072. injectUIEvent: function(target, options, view) {
  2073. var customEvent = null;
  2074. view = view || window;
  2075. // check for DOM-compliant browsers first
  2076. if (doc.createEvent) {
  2077. // just a generic UI Event object is needed
  2078. customEvent = doc.createEvent("UIEvents");
  2079. customEvent.initUIEvent(options.type, options.bubbles, options.cancelable, view, options.detail);
  2080. target.dispatchEvent(customEvent);
  2081. } else if (doc.createEventObject) {
  2082. // IE
  2083. customEvent = doc.createEventObject();
  2084. customEvent.bubbles = options.bubbles;
  2085. customEvent.cancelable = options.cancelable;
  2086. customEvent.view = view;
  2087. customEvent.detail = options.detail;
  2088. target.fireEvent("on" + options.type, customEvent);
  2089. } else {
  2090. return false;
  2091. }
  2092. return true;
  2093. }
  2094. }
  2095. };
  2096. });
  2097. // statics
  2098. /**
  2099. * @extends Ext.ux.event.Driver
  2100. * Event recorder.
  2101. */
  2102. Ext.define('Ext.ux.event.Recorder', function(Recorder) {
  2103. var eventsToRecord, eventKey;
  2104. function apply() {
  2105. var a = arguments,
  2106. n = a.length,
  2107. obj = {
  2108. kind: 'other'
  2109. },
  2110. i;
  2111. for (i = 0; i < n; ++i) {
  2112. Ext.apply(obj, arguments[i]);
  2113. }
  2114. if (obj.alt && !obj.event) {
  2115. obj.event = obj.alt;
  2116. }
  2117. return obj;
  2118. }
  2119. function key(extra) {
  2120. return apply({
  2121. kind: 'keyboard',
  2122. modKeys: true,
  2123. key: true
  2124. }, extra);
  2125. }
  2126. function mouse(extra) {
  2127. return apply({
  2128. kind: 'mouse',
  2129. button: true,
  2130. modKeys: true,
  2131. xy: true
  2132. }, extra);
  2133. }
  2134. eventsToRecord = {
  2135. keydown: key(),
  2136. keypress: key(),
  2137. keyup: key(),
  2138. dragmove: mouse({
  2139. alt: 'mousemove',
  2140. pageCoords: true,
  2141. whileDrag: true
  2142. }),
  2143. mousemove: mouse({
  2144. pageCoords: true
  2145. }),
  2146. mouseover: mouse(),
  2147. mouseout: mouse(),
  2148. click: mouse(),
  2149. wheel: mouse({
  2150. wheel: true
  2151. }),
  2152. mousedown: mouse({
  2153. press: true
  2154. }),
  2155. mouseup: mouse({
  2156. release: true
  2157. }),
  2158. scroll: apply({
  2159. listen: false
  2160. }),
  2161. focus: apply(),
  2162. blur: apply()
  2163. };
  2164. for (eventKey in eventsToRecord) {
  2165. if (!eventsToRecord[eventKey].event) {
  2166. eventsToRecord[eventKey].event = eventKey;
  2167. }
  2168. }
  2169. eventsToRecord.wheel.event = null;
  2170. // must detect later
  2171. return {
  2172. extend: 'Ext.ux.event.Driver',
  2173. /**
  2174. * @event add
  2175. * Fires when an event is added to the recording.
  2176. * @param {Ext.ux.event.Recorder} this
  2177. * @param {Object} eventDescriptor The event descriptor.
  2178. */
  2179. /**
  2180. * @event coalesce
  2181. * Fires when an event is coalesced. This edits the tail of the recorded
  2182. * event list.
  2183. * @param {Ext.ux.event.Recorder} this
  2184. * @param {Object} eventDescriptor The event descriptor that was coalesced.
  2185. */
  2186. eventsToRecord: eventsToRecord,
  2187. ignoreIdRegEx: /ext-gen(?:\d+)/,
  2188. inputRe: /^(input|textarea)$/i,
  2189. constructor: function(config) {
  2190. var me = this,
  2191. events = config && config.eventsToRecord;
  2192. if (events) {
  2193. me.eventsToRecord = Ext.apply(Ext.apply({}, me.eventsToRecord), // duplicate
  2194. events);
  2195. // and merge
  2196. delete config.eventsToRecord;
  2197. }
  2198. // don't smash
  2199. me.callParent(arguments);
  2200. me.clear();
  2201. me.modKeys = [];
  2202. me.attachTo = me.attachTo || window;
  2203. },
  2204. clear: function() {
  2205. this.eventsRecorded = [];
  2206. },
  2207. listenToEvent: function(event) {
  2208. var me = this,
  2209. el = me.attachTo.document.body,
  2210. fn = function() {
  2211. return me.onEvent.apply(me, arguments);
  2212. },
  2213. cleaner = {};
  2214. if (el.attachEvent && el.ownerDocument.documentMode < 10) {
  2215. event = 'on' + event;
  2216. el.attachEvent(event, fn);
  2217. cleaner.destroy = function() {
  2218. if (fn) {
  2219. el.detachEvent(event, fn);
  2220. fn = null;
  2221. }
  2222. };
  2223. } else {
  2224. el.addEventListener(event, fn, true);
  2225. cleaner.destroy = function() {
  2226. if (fn) {
  2227. el.removeEventListener(event, fn, true);
  2228. fn = null;
  2229. }
  2230. };
  2231. }
  2232. return cleaner;
  2233. },
  2234. coalesce: function(rec, ev) {
  2235. var me = this,
  2236. events = me.eventsRecorded,
  2237. length = events.length,
  2238. tail = length && events[length - 1],
  2239. tail2 = (length > 1) && events[length - 2],
  2240. tail3 = (length > 2) && events[length - 3];
  2241. if (!tail) {
  2242. return false;
  2243. }
  2244. if (rec.type === 'mousemove') {
  2245. if (tail.type === 'mousemove' && rec.ts - tail.ts < 200) {
  2246. rec.ts = tail.ts;
  2247. events[length - 1] = rec;
  2248. return true;
  2249. }
  2250. } else if (rec.type === 'click') {
  2251. if (tail2 && tail.type === 'mouseup' && tail2.type === 'mousedown') {
  2252. if (rec.button === tail.button && rec.button === tail2.button && rec.target === tail.target && rec.target === tail2.target && me.samePt(rec, tail) && me.samePt(rec, tail2)) {
  2253. events.pop();
  2254. // remove mouseup
  2255. tail2.type = 'mduclick';
  2256. return true;
  2257. }
  2258. }
  2259. } else if (rec.type === 'keyup') {
  2260. // tail3 = { type: "type", text: "..." },
  2261. // tail2 = { type: "keydown", charCode: 65, keyCode: 65 },
  2262. // tail = { type: "keypress", charCode: 97, keyCode: 97 },
  2263. // rec = { type: "keyup", charCode: 65, keyCode: 65 },
  2264. if (tail2 && tail.type === 'keypress' && tail2.type === 'keydown') {
  2265. if (rec.target === tail.target && rec.target === tail2.target) {
  2266. events.pop();
  2267. // remove keypress
  2268. tail2.type = 'type';
  2269. tail2.text = String.fromCharCode(tail.charCode);
  2270. delete tail2.charCode;
  2271. delete tail2.keyCode;
  2272. if (tail3 && tail3.type === 'type') {
  2273. if (tail3.text && tail3.target === tail2.target) {
  2274. tail3.text += tail2.text;
  2275. events.pop();
  2276. }
  2277. }
  2278. return true;
  2279. }
  2280. }
  2281. // tail = { type: "keydown", charCode: 40, keyCode: 40 },
  2282. // rec = { type: "keyup", charCode: 40, keyCode: 40 },
  2283. else if (me.completeKeyStroke(tail, rec)) {
  2284. tail.type = 'type';
  2285. me.completeSpecialKeyStroke(ev.target, tail, rec);
  2286. return true;
  2287. }
  2288. // tail2 = { type: "keydown", charCode: 40, keyCode: 40 },
  2289. // tail = { type: "scroll", ... },
  2290. // rec = { type: "keyup", charCode: 40, keyCode: 40 },
  2291. else if (tail.type === 'scroll' && me.completeKeyStroke(tail2, rec)) {
  2292. tail2.type = 'type';
  2293. me.completeSpecialKeyStroke(ev.target, tail2, rec);
  2294. // swap the order of type and scroll events
  2295. events.pop();
  2296. events.pop();
  2297. events.push(tail, tail2);
  2298. return true;
  2299. }
  2300. }
  2301. return false;
  2302. },
  2303. completeKeyStroke: function(down, up) {
  2304. if (down && down.type === 'keydown' && down.keyCode === up.keyCode) {
  2305. delete down.charCode;
  2306. return true;
  2307. }
  2308. return false;
  2309. },
  2310. completeSpecialKeyStroke: function(target, down, up) {
  2311. var key = this.specialKeysByCode[up.keyCode];
  2312. if (key && this.inputRe.test(target.tagName)) {
  2313. // home,end,arrow keys + shift get crazy, so encode selection/caret
  2314. delete down.keyCode;
  2315. down.key = key;
  2316. down.selection = this.getTextSelection(target);
  2317. if (down.selection[0] === down.selection[1]) {
  2318. down.caret = down.selection[0];
  2319. delete down.selection;
  2320. }
  2321. return true;
  2322. }
  2323. return false;
  2324. },
  2325. getElementXPath: function(el) {
  2326. var me = this,
  2327. good = false,
  2328. xpath = [],
  2329. count, sibling, t, tag;
  2330. for (t = el; t; t = t.parentNode) {
  2331. if (t === me.attachTo.document.body) {
  2332. xpath.unshift('~');
  2333. good = true;
  2334. break;
  2335. }
  2336. if (t.id && !me.ignoreIdRegEx.test(t.id)) {
  2337. xpath.unshift('#' + t.id);
  2338. good = true;
  2339. break;
  2340. }
  2341. for (count = 1 , sibling = t; !!(sibling = sibling.previousSibling); ) {
  2342. if (sibling.tagName === t.tagName) {
  2343. ++count;
  2344. }
  2345. }
  2346. tag = t.tagName.toLowerCase();
  2347. if (count < 2) {
  2348. xpath.unshift(tag);
  2349. } else {
  2350. xpath.unshift(tag + '[' + count + ']');
  2351. }
  2352. }
  2353. return good ? xpath.join('/') : null;
  2354. },
  2355. getRecordedEvents: function() {
  2356. return this.eventsRecorded;
  2357. },
  2358. onEvent: function(ev) {
  2359. var me = this,
  2360. e = new Ext.event.Event(ev),
  2361. info = me.eventsToRecord[e.type],
  2362. root, modKeys, elXY,
  2363. rec = {
  2364. type: e.type,
  2365. ts: me.getTimestamp(),
  2366. target: me.getElementXPath(e.target)
  2367. },
  2368. xy;
  2369. if (!info || !rec.target) {
  2370. return;
  2371. }
  2372. root = e.target.ownerDocument;
  2373. root = root.defaultView || root.parentWindow;
  2374. // Standards || IE
  2375. if (root !== me.attachTo) {
  2376. return;
  2377. }
  2378. if (me.eventsToRecord.scroll) {
  2379. me.syncScroll(e.target);
  2380. }
  2381. if (info.xy) {
  2382. xy = e.getXY();
  2383. if (info.pageCoords || !rec.target) {
  2384. rec.px = xy[0];
  2385. rec.py = xy[1];
  2386. } else {
  2387. elXY = Ext.fly(e.getTarget()).getXY();
  2388. xy[0] -= elXY[0];
  2389. xy[1] -= elXY[1];
  2390. rec.x = xy[0];
  2391. rec.y = xy[1];
  2392. }
  2393. }
  2394. if (info.button) {
  2395. if ('buttons' in ev) {
  2396. rec.button = ev.buttons;
  2397. } else // LEFT=1, RIGHT=2, MIDDLE=4, etc.
  2398. {
  2399. rec.button = ev.button;
  2400. }
  2401. if (!rec.button && info.whileDrag) {
  2402. return;
  2403. }
  2404. }
  2405. if (info.wheel) {
  2406. rec.type = 'wheel';
  2407. if (info.event === 'wheel') {
  2408. // Current FireFox (technically IE9+ if we use addEventListener but
  2409. // checking document.onwheel does not detect this)
  2410. rec.dx = ev.deltaX;
  2411. rec.dy = ev.deltaY;
  2412. } else if (typeof ev.wheelDeltaX === 'number') {
  2413. // new WebKit has both X & Y
  2414. rec.dx = -1 / 40 * ev.wheelDeltaX;
  2415. rec.dy = -1 / 40 * ev.wheelDeltaY;
  2416. } else if (ev.wheelDelta) {
  2417. // old WebKit and IE
  2418. rec.dy = -1 / 40 * ev.wheelDelta;
  2419. } else if (ev.detail) {
  2420. // Old Gecko
  2421. rec.dy = ev.detail;
  2422. }
  2423. }
  2424. if (info.modKeys) {
  2425. me.modKeys[0] = e.altKey ? 'A' : '';
  2426. me.modKeys[1] = e.ctrlKey ? 'C' : '';
  2427. me.modKeys[2] = e.metaKey ? 'M' : '';
  2428. me.modKeys[3] = e.shiftKey ? 'S' : '';
  2429. modKeys = me.modKeys.join('');
  2430. if (modKeys) {
  2431. rec.modKeys = modKeys;
  2432. }
  2433. }
  2434. if (info.key) {
  2435. rec.charCode = e.getCharCode();
  2436. rec.keyCode = e.getKey();
  2437. }
  2438. if (me.coalesce(rec, e)) {
  2439. me.fireEvent('coalesce', me, rec);
  2440. } else {
  2441. me.eventsRecorded.push(rec);
  2442. me.fireEvent('add', me, rec);
  2443. }
  2444. },
  2445. onStart: function() {
  2446. var me = this,
  2447. ddm = me.attachTo.Ext.dd.DragDropManager,
  2448. evproto = me.attachTo.Ext.EventObjectImpl.prototype,
  2449. special = [];
  2450. // FireFox does not support the 'mousewheel' event but does support the
  2451. // 'wheel' event instead.
  2452. Recorder.prototype.eventsToRecord.wheel.event = ('onwheel' in me.attachTo.document) ? 'wheel' : 'mousewheel';
  2453. me.listeners = [];
  2454. Ext.Object.each(me.eventsToRecord, function(name, value) {
  2455. if (value && value.listen !== false) {
  2456. if (!value.event) {
  2457. value.event = name;
  2458. }
  2459. if (value.alt && value.alt !== name) {
  2460. // The 'drag' event is just mousemove while buttons are pressed,
  2461. // so if there is a mousemove entry as well, ignore the drag
  2462. if (!me.eventsToRecord[value.alt]) {
  2463. special.push(value);
  2464. }
  2465. } else {
  2466. me.listeners.push(me.listenToEvent(value.event));
  2467. }
  2468. }
  2469. });
  2470. Ext.each(special, function(info) {
  2471. me.eventsToRecord[info.alt] = info;
  2472. me.listeners.push(me.listenToEvent(info.alt));
  2473. });
  2474. me.ddmStopEvent = ddm.stopEvent;
  2475. ddm.stopEvent = Ext.Function.createSequence(ddm.stopEvent, function(e) {
  2476. me.onEvent(e);
  2477. });
  2478. me.evStopEvent = evproto.stopEvent;
  2479. evproto.stopEvent = Ext.Function.createSequence(evproto.stopEvent, function() {
  2480. me.onEvent(this);
  2481. });
  2482. },
  2483. onStop: function() {
  2484. var me = this;
  2485. Ext.destroy(me.listeners);
  2486. me.listeners = null;
  2487. me.attachTo.Ext.dd.DragDropManager.stopEvent = me.ddmStopEvent;
  2488. me.attachTo.Ext.EventObjectImpl.prototype.stopEvent = me.evStopEvent;
  2489. },
  2490. samePt: function(pt1, pt2) {
  2491. return pt1.x === pt2.x && pt1.y === pt2.y;
  2492. },
  2493. syncScroll: function(el) {
  2494. var me = this,
  2495. ts = me.getTimestamp(),
  2496. oldX, oldY, x, y, scrolled, rec, p;
  2497. for (p = el; p; p = p.parentNode) {
  2498. oldX = p.$lastScrollLeft;
  2499. oldY = p.$lastScrollTop;
  2500. x = p.scrollLeft;
  2501. y = p.scrollTop;
  2502. scrolled = false;
  2503. if (oldX !== x) {
  2504. if (x) {
  2505. scrolled = true;
  2506. }
  2507. p.$lastScrollLeft = x;
  2508. }
  2509. if (oldY !== y) {
  2510. if (y) {
  2511. scrolled = true;
  2512. }
  2513. p.$lastScrollTop = y;
  2514. }
  2515. if (scrolled) {
  2516. // console.log('scroll x:' + x + ' y:' + y, p);
  2517. me.eventsRecorded.push(rec = {
  2518. type: 'scroll',
  2519. target: me.getElementXPath(p),
  2520. ts: ts,
  2521. pos: [
  2522. x,
  2523. y
  2524. ]
  2525. });
  2526. me.fireEvent('add', me, rec);
  2527. }
  2528. if (p.tagName === 'BODY') {
  2529. break;
  2530. }
  2531. }
  2532. }
  2533. };
  2534. });
  2535. /**
  2536. * Describes a gauge needle as a shape defined in SVG path syntax.
  2537. *
  2538. * Note: this class and its subclasses are not supposed to be instantiated directly
  2539. * - an object should be passed the gauge's {@link Ext.ux.gauge.Gauge#needle}
  2540. * config instead. Needle instances are also not supposed to be moved
  2541. * between gauges.
  2542. */
  2543. Ext.define('Ext.ux.gauge.needle.Abstract', {
  2544. mixins: [
  2545. 'Ext.mixin.Factoryable'
  2546. ],
  2547. alias: 'gauge.needle.abstract',
  2548. isNeedle: true,
  2549. config: {
  2550. /**
  2551. * The generator function for the needle's shape.
  2552. * Because the gauge component is resizable, and it is generally
  2553. * desirable to resize the needle along with the gauge, the needle's
  2554. * shape should have an ability to grow, typically non-uniformly,
  2555. * which necessitates a generator function that will update the needle's
  2556. * path, so that its proportions are appropriate for the current gauge size.
  2557. *
  2558. * The generator function is given two parameters: the inner and outer
  2559. * radius of the needle. For example, for a straight arrow, the path
  2560. * definition is expected to have the base of the needle at the origin
  2561. * - (0, 0) coordinates - and point downwards. The needle will be automatically
  2562. * translated to the center of the gauge and rotated to represent the current
  2563. * gauge {@link Ext.ux.gauge.Gauge#value value}.
  2564. *
  2565. * @param {Function} path The path generator function.
  2566. * @param {Number} path.innerRadius The function's first parameter.
  2567. * @param {Number} path.outerRadius The function's second parameter.
  2568. * @return {String} path.return The shape of the needle in the SVG path syntax returned by
  2569. * the generator function.
  2570. */
  2571. path: null,
  2572. /**
  2573. * The inner radius of the needle. This works just like the `innerRadius`
  2574. * config of the {@link Ext.ux.gauge.Gauge#trackStyle}.
  2575. * The default value is `25` to make sure the needle doesn't overlap with
  2576. * the value of the gauge shown at its center by default.
  2577. *
  2578. * @param {Number/String} [innerRadius=25]
  2579. */
  2580. innerRadius: 25,
  2581. /**
  2582. * The outer radius of the needle. This works just like the `outerRadius`
  2583. * config of the {@link Ext.ux.gauge.Gauge#trackStyle}.
  2584. *
  2585. * @param {Number/String} [outerRadius='100% - 20']
  2586. */
  2587. outerRadius: '100% - 20',
  2588. /**
  2589. * The shape generated by the {@link #path} function is used as the value
  2590. * for the `d` attribute of the SVG `<path>` element. This element
  2591. * has the default class name of `.x-gauge-needle`, so that CSS can be used
  2592. * to give all gauge needles some common styling. To style a particular needle,
  2593. * one can use this config to add styles to the needle's `<path>` element directly,
  2594. * or use a custom {@link Ext.ux.gauge.Gauge#cls class} for the needle's gauge
  2595. * and style the needle from there.
  2596. *
  2597. * This config is not supposed to be updated manually, the styles should
  2598. * always be updated by the means of the `setStyle` call. For example,
  2599. * this is not allowed:
  2600. *
  2601. * gauge.getStyle().fill = 'red'; // WRONG!
  2602. * gauge.setStyle({ 'fill': 'red' }); // correct
  2603. *
  2604. * Subsequent calls to the `setStyle` will add to the styles set previously
  2605. * or overwrite their values, but won't remove them. If you'd like to style
  2606. * from a clean slate, setting the style to `null` first will remove the styles
  2607. * previously set:
  2608. *
  2609. * gauge.getNeedle().setStyle(null);
  2610. *
  2611. * If an SVG shape was produced by a designer rather than programmatically,
  2612. * in other words, the {@link #path} function returns the same shape regardless
  2613. * of the parameters it was given, the uniform scaling of said shape is the only
  2614. * option, if one wants to use gauges of different sizes. In this case,
  2615. * it's possible to specify the desired scale by using the `transform` style,
  2616. * for example:
  2617. *
  2618. * transform: 'scale(0.35)'
  2619. *
  2620. * @param {Object} style
  2621. */
  2622. style: null,
  2623. /**
  2624. * @private
  2625. * @param {Number} radius
  2626. */
  2627. radius: 0,
  2628. /**
  2629. * @private
  2630. * Expected in the initial config, required during construction.
  2631. * @param {Ext.ux.gauge.Gauge} gauge
  2632. */
  2633. gauge: null
  2634. },
  2635. constructor: function(config) {
  2636. this.initConfig(config);
  2637. },
  2638. applyInnerRadius: function(innerRadius) {
  2639. return this.getGauge().getRadiusFn(innerRadius);
  2640. },
  2641. applyOuterRadius: function(outerRadius) {
  2642. return this.getGauge().getRadiusFn(outerRadius);
  2643. },
  2644. updateRadius: function() {
  2645. this.regeneratePath();
  2646. },
  2647. setTransform: function(centerX, centerY, rotation) {
  2648. var needleGroup = this.getNeedleGroup();
  2649. needleGroup.setStyle('transform', 'translate(' + centerX + 'px,' + centerY + 'px) ' + 'rotate(' + rotation + 'deg)');
  2650. },
  2651. applyPath: function(path) {
  2652. return Ext.isFunction(path) ? path : null;
  2653. },
  2654. updatePath: function(path) {
  2655. this.regeneratePath(path);
  2656. },
  2657. regeneratePath: function(path) {
  2658. path = path || this.getPath();
  2659. // eslint-disable-next-line vars-on-top
  2660. var me = this,
  2661. radius = me.getRadius(),
  2662. inner = me.getInnerRadius()(radius),
  2663. outer = me.getOuterRadius()(radius),
  2664. d = outer > inner ? path(inner, outer) : '';
  2665. me.getNeedlePath().dom.setAttribute('d', d);
  2666. },
  2667. getNeedleGroup: function() {
  2668. var gauge = this.getGauge(),
  2669. group = this.needleGroup;
  2670. // The gauge positions the needle by calling its `setTransform` method,
  2671. // which applies a transformation to the needle's group, that contains
  2672. // the actual path element. This is done because we need the ability to
  2673. // transform the path independently from it's position in the gauge.
  2674. // For example, if the needle has to be made bigger, is shouldn't be
  2675. // part of the transform that centers it in the gauge and rotates it
  2676. // to point at the current value.
  2677. if (!group) {
  2678. group = this.needleGroup = Ext.get(document.createElementNS(gauge.svgNS, 'g'));
  2679. gauge.getSvg().appendChild(group);
  2680. }
  2681. return group;
  2682. },
  2683. getNeedlePath: function() {
  2684. var me = this,
  2685. pathElement = me.pathElement;
  2686. if (!pathElement) {
  2687. pathElement = me.pathElement = Ext.get(document.createElementNS(me.getGauge().svgNS, 'path'));
  2688. pathElement.dom.setAttribute('class', Ext.baseCSSPrefix + 'gauge-needle');
  2689. me.getNeedleGroup().appendChild(pathElement);
  2690. }
  2691. return pathElement;
  2692. },
  2693. updateStyle: function(style) {
  2694. var pathElement = this.getNeedlePath();
  2695. // Note that we are setting the `style` attribute, e.g `style="fill: red"`,
  2696. // instead of path attributes individually, e.g. `fill="red"` because
  2697. // the attribute styles defined in CSS classes will override the values
  2698. // of attributes set on the elements individually.
  2699. if (Ext.isObject(style)) {
  2700. pathElement.setStyle(style);
  2701. } else {
  2702. pathElement.dom.removeAttribute('style');
  2703. }
  2704. },
  2705. destroy: function() {
  2706. var me = this;
  2707. me.pathElement = Ext.destroy(me.pathElement);
  2708. me.needleGroup = Ext.destroy(me.needleGroup);
  2709. me.setGauge(null);
  2710. }
  2711. });
  2712. /**
  2713. * Displays a value within the given interval as a gauge. For example:
  2714. *
  2715. * @example
  2716. * Ext.create({
  2717. * xtype: 'panel',
  2718. * renderTo: document.body,
  2719. * width: 200,
  2720. * height: 200,
  2721. * layout: 'fit',
  2722. * items: {
  2723. * xtype: 'gauge',
  2724. * padding: 20,
  2725. * value: 55,
  2726. * minValue: 40,
  2727. * maxValue: 80
  2728. * }
  2729. * });
  2730. *
  2731. * It's also possible to use gauges to create loading indicators:
  2732. *
  2733. * @example
  2734. * Ext.create({
  2735. * xtype: 'panel',
  2736. * renderTo: document.body,
  2737. * width: 200,
  2738. * height: 200,
  2739. * layout: 'fit',
  2740. * items: {
  2741. * xtype: 'gauge',
  2742. * padding: 20,
  2743. * trackStart: 0,
  2744. * trackLength: 360,
  2745. * value: 20,
  2746. * valueStyle: {
  2747. * round: true
  2748. * },
  2749. * textTpl: 'Loading...',
  2750. * animation: {
  2751. * easing: 'linear',
  2752. * duration: 100000
  2753. * }
  2754. * }
  2755. * }).items.first().setAngleOffset(360 * 100);
  2756. *
  2757. * Gauges can contain needles as well.
  2758. *
  2759. * @example
  2760. * Ext.create({
  2761. * xtype: 'panel',
  2762. * renderTo: document.body,
  2763. * width: 200,
  2764. * height: 200,
  2765. * layout: 'fit',
  2766. * items: {
  2767. * xtype: 'gauge',
  2768. * padding: 20,
  2769. * value: 55,
  2770. * minValue: 40,
  2771. * maxValue: 80,
  2772. * needle: 'wedge'
  2773. * }
  2774. * });
  2775. *
  2776. */
  2777. Ext.define('Ext.ux.gauge.Gauge', {
  2778. alternateClassName: 'Ext.ux.Gauge',
  2779. extend: 'Ext.Gadget',
  2780. xtype: 'gauge',
  2781. requires: [
  2782. 'Ext.ux.gauge.needle.Abstract',
  2783. 'Ext.util.Region'
  2784. ],
  2785. config: {
  2786. /**
  2787. * @cfg {Number/String} padding
  2788. * Gauge sector padding in pixels or percent of width/height, whichever is smaller.
  2789. */
  2790. padding: 10,
  2791. /**
  2792. * @cfg {Number} trackStart
  2793. * The angle in the [0, 360) interval at which the gauge's track sector starts.
  2794. * E.g. 0 for 3 o-clock, 90 for 6 o-clock, 180 for 9 o-clock, 270 for noon.
  2795. */
  2796. trackStart: 135,
  2797. /**
  2798. * @cfg {Number} trackLength
  2799. * The angle in the (0, 360] interval to add to the {@link #trackStart} angle
  2800. * to determine the angle at which the track ends.
  2801. */
  2802. trackLength: 270,
  2803. /**
  2804. * @cfg {Number} angleOffset
  2805. * The angle at which the {@link #minValue} starts in case of a circular gauge.
  2806. */
  2807. angleOffset: 0,
  2808. /**
  2809. * @cfg {Number} minValue
  2810. * The minimum value that the gauge can represent.
  2811. */
  2812. minValue: 0,
  2813. /**
  2814. * @cfg {Number} maxValue
  2815. * The maximum value that the gauge can represent.
  2816. */
  2817. maxValue: 100,
  2818. /**
  2819. * @cfg {Number} value
  2820. * The current value of the gauge.
  2821. */
  2822. value: 50,
  2823. /**
  2824. * @cfg {Ext.ux.gauge.needle.Abstract} needle
  2825. * A config object for the needle to be used by the gauge.
  2826. * The needle will track the current {@link #value}.
  2827. * The default needle type is 'diamond', so if a config like
  2828. *
  2829. * needle: {
  2830. * outerRadius: '100%'
  2831. * }
  2832. *
  2833. * is used, the app/view still has to require
  2834. * the `Ext.ux.gauge.needle.Diamond` class.
  2835. * If a type is specified explicitly
  2836. *
  2837. * needle: {
  2838. * type: 'arrow'
  2839. * }
  2840. *
  2841. * it's straightforward which class should be required.
  2842. */
  2843. needle: null,
  2844. needleDefaults: {
  2845. cached: true,
  2846. $value: {
  2847. type: 'diamond'
  2848. }
  2849. },
  2850. /**
  2851. * @cfg {Boolean} [clockwise=true]
  2852. * `true` - {@link #cfg!value} increments in a clockwise fashion
  2853. * `false` - {@link #cfg!value} increments in an anticlockwise fashion
  2854. */
  2855. clockwise: true,
  2856. /**
  2857. * @cfg {Ext.XTemplate} textTpl
  2858. * The template for the text in the center of the gauge.
  2859. * The available data values are:
  2860. * - `value` - The {@link #cfg!value} of the gauge.
  2861. * - `percent` - The value as a percentage between 0 and 100.
  2862. * - `minValue` - The value of the {@link #cfg!minValue} config.
  2863. * - `maxValue` - The value of the {@link #cfg!maxValue} config.
  2864. * - `delta` - The delta between the {@link #cfg!minValue} and {@link #cfg!maxValue}.
  2865. */
  2866. textTpl: [
  2867. '<tpl>{value:number("0.00")}%</tpl>'
  2868. ],
  2869. /**
  2870. * @cfg {String} [textAlign='c-c']
  2871. * If the gauge has a donut hole, the text will be centered inside it.
  2872. * Otherwise, the text will be centered in the middle of the gauge's
  2873. * bounding box. This config allows to alter the position of the text
  2874. * in the latter case. See the docs for the `align` option to the
  2875. * {@link Ext.util.Region#alignTo} method for possible ways of alignment
  2876. * of the text to the guage's bounding box.
  2877. */
  2878. textAlign: 'c-c',
  2879. /**
  2880. * @cfg {Object} textOffset
  2881. * This config can be used to displace the {@link #textTpl text} from its default
  2882. * position in the center of the gauge by providing values for horizontal and
  2883. * vertical displacement.
  2884. * @cfg {Number} textOffset.dx Horizontal displacement.
  2885. * @cfg {Number} textOffset.dy Vertical displacement.
  2886. */
  2887. textOffset: {
  2888. dx: 0,
  2889. dy: 0
  2890. },
  2891. /**
  2892. * @cfg {Object} trackStyle
  2893. * Track sector styles.
  2894. * @cfg {String/Object[]} trackStyle.fill Track sector fill color. Defaults to CSS value.
  2895. * It's also possible to have a linear gradient fill that starts at the top-left corner
  2896. * of the gauge and ends at its bottom-right corner, by providing an array of color stop
  2897. * objects. For example:
  2898. *
  2899. * trackStyle: {
  2900. * fill: [{
  2901. * offset: 0,
  2902. * color: 'green',
  2903. * opacity: 0.8
  2904. * }, {
  2905. * offset: 1,
  2906. * color: 'gold'
  2907. * }]
  2908. * }
  2909. *
  2910. * @cfg {Number} trackStyle.fillOpacity Track sector fill opacity. Defaults to CSS value.
  2911. * @cfg {String} trackStyle.stroke Track sector stroke color. Defaults to CSS value.
  2912. * @cfg {Number} trackStyle.strokeOpacity Track sector stroke opacity.
  2913. * Defaults to CSS value.
  2914. * @cfg {Number} trackStyle.strokeWidth Track sector stroke width. Defaults to CSS value.
  2915. * @cfg {Number/String} [trackStyle.outerRadius='100%'] The outer radius of the track
  2916. * sector.
  2917. * For example:
  2918. *
  2919. * outerRadius: '90%', // 90% of the maximum radius
  2920. * outerRadius: 100, // radius of 100 pixels
  2921. * outerRadius: '70% + 5', // 70% of the maximum radius plus 5 pixels
  2922. * outerRadius: '80% - 10', // 80% of the maximum radius minus 10 pixels
  2923. *
  2924. * @cfg {Number/String} [trackStyle.innerRadius='50%'] The inner radius of the track sector.
  2925. * See the `trackStyle.outerRadius` config documentation for more information.
  2926. * @cfg {Boolean} [trackStyle.round=false] Whether to round the track sector edges or not.
  2927. */
  2928. trackStyle: {
  2929. outerRadius: '100%',
  2930. innerRadius: '100% - 20',
  2931. round: false
  2932. },
  2933. /**
  2934. * @cfg {Object} valueStyle
  2935. * Value sector styles.
  2936. * @cfg {String/Object[]} valueStyle.fill Value sector fill color. Defaults to CSS value.
  2937. * See the `trackStyle.fill` config documentation for more information.
  2938. * @cfg {Number} valueStyle.fillOpacity Value sector fill opacity. Defaults to CSS value.
  2939. * @cfg {String} valueStyle.stroke Value sector stroke color. Defaults to CSS value.
  2940. * @cfg {Number} valueStyle.strokeOpacity Value sector stroke opacity. Defaults to
  2941. * CSS value.
  2942. * @cfg {Number} valueStyle.strokeWidth Value sector stroke width. Defaults to CSS value.
  2943. * @cfg {Number/String} [valueStyle.outerRadius='100% - 4'] The outer radius of the value
  2944. * sector.
  2945. * See the `trackStyle.outerRadius` config documentation for more information.
  2946. * @cfg {Number/String} [valueStyle.innerRadius='50% + 4'] The inner radius of the value
  2947. * sector.
  2948. * See the `trackStyle.outerRadius` config documentation for more information.
  2949. * @cfg {Boolean} [valueStyle.round=false] Whether to round the value sector edges or not.
  2950. */
  2951. valueStyle: {
  2952. outerRadius: '100% - 2',
  2953. innerRadius: '100% - 18',
  2954. round: false
  2955. },
  2956. /**
  2957. * @cfg {Object/Boolean} [animation=true]
  2958. * The animation applied to the gauge on changes to the {@link #value}
  2959. * and the {@link #angleOffset} configs. Defaults to 1 second animation
  2960. * with the 'out' easing.
  2961. * @cfg {Number} animation.duration The duraction of the animation.
  2962. * @cfg {String} animation.easing The easing function to use for the animation.
  2963. * Possible values are:
  2964. * - `linear` - no easing, no acceleration
  2965. * - `in` - accelerating from zero velocity
  2966. * - `out` - (default) decelerating to zero velocity
  2967. * - `inOut` - acceleration until halfway, then deceleration
  2968. */
  2969. animation: true
  2970. },
  2971. baseCls: Ext.baseCSSPrefix + 'gauge',
  2972. template: [
  2973. {
  2974. reference: 'bodyElement',
  2975. children: [
  2976. {
  2977. reference: 'textElement',
  2978. cls: Ext.baseCSSPrefix + 'gauge-text'
  2979. }
  2980. ]
  2981. }
  2982. ],
  2983. defaultBindProperty: 'value',
  2984. pathAttributes: {
  2985. // The properties in the `trackStyle` and `valueStyle` configs
  2986. // that are path attributes.
  2987. fill: true,
  2988. fillOpacity: true,
  2989. stroke: true,
  2990. strokeOpacity: true,
  2991. strokeWidth: true
  2992. },
  2993. easings: {
  2994. linear: Ext.identityFn,
  2995. // cubic easings
  2996. 'in': function(t) {
  2997. return t * t * t;
  2998. },
  2999. out: function(t) {
  3000. return (--t) * t * t + 1;
  3001. },
  3002. inOut: function(t) {
  3003. return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;
  3004. }
  3005. },
  3006. resizeDelay: 0,
  3007. // in milliseconds
  3008. resizeTimerId: 0,
  3009. size: null,
  3010. // cached size
  3011. svgNS: 'http://www.w3.org/2000/svg',
  3012. svg: null,
  3013. // SVG document
  3014. defs: null,
  3015. // the `defs` section of the SVG document
  3016. trackArc: null,
  3017. valueArc: null,
  3018. trackGradient: null,
  3019. valueGradient: null,
  3020. fx: null,
  3021. // either the `value` or the `angleOffset` animation
  3022. fxValue: 0,
  3023. // the actual value rendered/animated
  3024. fxAngleOffset: 0,
  3025. constructor: function(config) {
  3026. var me = this;
  3027. me.fitSectorInRectCache = {
  3028. startAngle: null,
  3029. lengthAngle: null,
  3030. minX: null,
  3031. maxX: null,
  3032. minY: null,
  3033. maxY: null
  3034. };
  3035. me.interpolator = me.createInterpolator();
  3036. me.callParent([
  3037. config
  3038. ]);
  3039. me.el.on('resize', 'onElementResize', me);
  3040. },
  3041. doDestroy: function() {
  3042. var me = this;
  3043. Ext.undefer(me.resizeTimerId);
  3044. me.el.un('resize', 'onElementResize', me);
  3045. me.stopAnimation();
  3046. me.setNeedle(null);
  3047. me.trackGradient = Ext.destroy(me.trackGradient);
  3048. me.valueGradient = Ext.destroy(me.valueGradient);
  3049. me.defs = Ext.destroy(me.defs);
  3050. me.svg = Ext.destroy(me.svg);
  3051. me.callParent();
  3052. },
  3053. onElementResize: function(element, size) {
  3054. this.handleResize(size);
  3055. },
  3056. handleResize: function(size, instantly) {
  3057. var me = this,
  3058. el = me.element;
  3059. if (!(el && (size = size || el.getSize()) && size.width && size.height)) {
  3060. return;
  3061. }
  3062. me.resizeTimerId = Ext.undefer(me.resizeTimerId);
  3063. if (!instantly && me.resizeDelay) {
  3064. me.resizeTimerId = Ext.defer(me.handleResize, me.resizeDelay, me, [
  3065. size,
  3066. true
  3067. ]);
  3068. return;
  3069. }
  3070. me.size = size;
  3071. me.resizeHandler(size);
  3072. },
  3073. updateMinValue: function(minValue) {
  3074. var me = this;
  3075. me.interpolator.setDomain(minValue, me.getMaxValue());
  3076. if (!me.isConfiguring) {
  3077. me.render();
  3078. }
  3079. },
  3080. updateMaxValue: function(maxValue) {
  3081. var me = this;
  3082. me.interpolator.setDomain(me.getMinValue(), maxValue);
  3083. if (!me.isConfiguring) {
  3084. me.render();
  3085. }
  3086. },
  3087. updateAngleOffset: function(angleOffset, oldAngleOffset) {
  3088. var me = this,
  3089. animation = me.getAnimation();
  3090. me.fxAngleOffset = angleOffset;
  3091. if (me.isConfiguring) {
  3092. return;
  3093. }
  3094. if (animation.duration) {
  3095. me.animate(oldAngleOffset, angleOffset, animation.duration, me.easings[animation.easing], function(angleOffset) {
  3096. me.fxAngleOffset = angleOffset;
  3097. me.render();
  3098. });
  3099. } else {
  3100. me.render();
  3101. }
  3102. },
  3103. //<debug>
  3104. applyTrackStart: function(trackStart) {
  3105. if (trackStart < 0 || trackStart >= 360) {
  3106. Ext.raise("'trackStart' should be within [0, 360).");
  3107. }
  3108. return trackStart;
  3109. },
  3110. applyTrackLength: function(trackLength) {
  3111. if (trackLength <= 0 || trackLength > 360) {
  3112. Ext.raise("'trackLength' should be within (0, 360].");
  3113. }
  3114. return trackLength;
  3115. },
  3116. //</debug>
  3117. updateTrackStart: function(trackStart) {
  3118. var me = this;
  3119. if (!me.isConfiguring) {
  3120. me.render();
  3121. }
  3122. },
  3123. updateTrackLength: function(trackLength) {
  3124. var me = this;
  3125. me.interpolator.setRange(0, trackLength);
  3126. if (!me.isConfiguring) {
  3127. me.render();
  3128. }
  3129. },
  3130. applyPadding: function(padding) {
  3131. var ratio;
  3132. if (typeof padding === 'string') {
  3133. ratio = parseFloat(padding) / 100;
  3134. return function(x) {
  3135. return x * ratio;
  3136. };
  3137. }
  3138. return function() {
  3139. return padding;
  3140. };
  3141. },
  3142. updatePadding: function() {
  3143. if (!this.isConfiguring) {
  3144. this.render();
  3145. }
  3146. },
  3147. applyValue: function(value) {
  3148. var minValue = this.getMinValue(),
  3149. maxValue = this.getMaxValue();
  3150. return Math.min(Math.max(value, minValue), maxValue);
  3151. },
  3152. updateValue: function(value, oldValue) {
  3153. var me = this,
  3154. animation = me.getAnimation();
  3155. me.fxValue = value;
  3156. if (me.isConfiguring) {
  3157. return;
  3158. }
  3159. me.writeText();
  3160. if (animation.duration) {
  3161. me.animate(oldValue, value, animation.duration, me.easings[animation.easing], function(value) {
  3162. me.fxValue = value;
  3163. me.render();
  3164. });
  3165. } else {
  3166. me.render();
  3167. }
  3168. },
  3169. applyTextTpl: function(textTpl) {
  3170. if (textTpl && !textTpl.isTemplate) {
  3171. textTpl = new Ext.XTemplate(textTpl);
  3172. }
  3173. return textTpl;
  3174. },
  3175. applyTextOffset: function(offset) {
  3176. offset = offset || {};
  3177. offset.dx = offset.dx || 0;
  3178. offset.dy = offset.dy || 0;
  3179. return offset;
  3180. },
  3181. updateTextTpl: function() {
  3182. this.writeText();
  3183. if (!this.isConfiguring) {
  3184. this.centerText();
  3185. }
  3186. },
  3187. // text will be centered on first size
  3188. writeText: function(options) {
  3189. var me = this,
  3190. value = me.getValue(),
  3191. minValue = me.getMinValue(),
  3192. maxValue = me.getMaxValue(),
  3193. delta = maxValue - minValue,
  3194. textTpl = me.getTextTpl();
  3195. textTpl.overwrite(me.textElement, {
  3196. value: value,
  3197. percent: (value - minValue) / delta * 100,
  3198. minValue: minValue,
  3199. maxValue: maxValue,
  3200. delta: delta
  3201. });
  3202. },
  3203. centerText: function(cx, cy, sectorRegion, innerRadius, outerRadius) {
  3204. var textElement = this.textElement,
  3205. textAlign = this.getTextAlign(),
  3206. alignedRegion, textBox;
  3207. if (Ext.Number.isEqual(innerRadius, 0, 0.1) || sectorRegion.isOutOfBound({
  3208. x: cx,
  3209. y: cy
  3210. })) {
  3211. alignedRegion = textElement.getRegion().alignTo({
  3212. align: textAlign,
  3213. // align text region's center to sector region's center
  3214. target: sectorRegion
  3215. });
  3216. textElement.setLeft(alignedRegion.left);
  3217. textElement.setTop(alignedRegion.top);
  3218. } else {
  3219. textBox = textElement.getBox();
  3220. textElement.setLeft(cx - textBox.width / 2);
  3221. textElement.setTop(cy - textBox.height / 2);
  3222. }
  3223. },
  3224. camelCaseRe: /([a-z])([A-Z])/g,
  3225. /**
  3226. * @private
  3227. */
  3228. camelToHyphen: function(name) {
  3229. return name.replace(this.camelCaseRe, '$1-$2').toLowerCase();
  3230. },
  3231. applyTrackStyle: function(trackStyle) {
  3232. var me = this,
  3233. trackGradient;
  3234. trackStyle.innerRadius = me.getRadiusFn(trackStyle.innerRadius);
  3235. trackStyle.outerRadius = me.getRadiusFn(trackStyle.outerRadius);
  3236. if (Ext.isArray(trackStyle.fill)) {
  3237. trackGradient = me.getTrackGradient();
  3238. me.setGradientStops(trackGradient, trackStyle.fill);
  3239. trackStyle.fill = 'url(#' + trackGradient.dom.getAttribute('id') + ')';
  3240. }
  3241. return trackStyle;
  3242. },
  3243. updateTrackStyle: function(trackStyle) {
  3244. var me = this,
  3245. trackArc = Ext.fly(me.getTrackArc()),
  3246. name;
  3247. for (name in trackStyle) {
  3248. if (name in me.pathAttributes) {
  3249. trackArc.setStyle(me.camelToHyphen(name), trackStyle[name]);
  3250. } else {
  3251. trackArc.setStyle(name, trackStyle[name]);
  3252. }
  3253. }
  3254. },
  3255. applyValueStyle: function(valueStyle) {
  3256. var me = this,
  3257. valueGradient;
  3258. valueStyle.innerRadius = me.getRadiusFn(valueStyle.innerRadius);
  3259. valueStyle.outerRadius = me.getRadiusFn(valueStyle.outerRadius);
  3260. if (Ext.isArray(valueStyle.fill)) {
  3261. valueGradient = me.getValueGradient();
  3262. me.setGradientStops(valueGradient, valueStyle.fill);
  3263. valueStyle.fill = 'url(#' + valueGradient.dom.getAttribute('id') + ')';
  3264. }
  3265. return valueStyle;
  3266. },
  3267. updateValueStyle: function(valueStyle) {
  3268. var me = this,
  3269. valueArc = Ext.fly(me.getValueArc()),
  3270. name;
  3271. for (name in valueStyle) {
  3272. if (name in me.pathAttributes) {
  3273. valueArc.setStyle(me.camelToHyphen(name), valueStyle[name]);
  3274. } else {
  3275. valueArc.setStyle(name, valueStyle[name]);
  3276. }
  3277. }
  3278. },
  3279. /**
  3280. * @private
  3281. */
  3282. getRadiusFn: function(radius) {
  3283. var result, pos, ratio,
  3284. increment = 0;
  3285. if (Ext.isNumber(radius)) {
  3286. result = function() {
  3287. return radius;
  3288. };
  3289. } else if (Ext.isString(radius)) {
  3290. radius = radius.replace(/ /g, '');
  3291. ratio = parseFloat(radius) / 100;
  3292. pos = radius.search('%');
  3293. // E.g. '100% - 4'
  3294. if (pos < radius.length - 1) {
  3295. increment = parseFloat(radius.substr(pos + 1));
  3296. }
  3297. result = function(radius) {
  3298. return radius * ratio + increment;
  3299. };
  3300. result.ratio = ratio;
  3301. }
  3302. return result;
  3303. },
  3304. getSvg: function() {
  3305. var me = this,
  3306. svg = me.svg;
  3307. if (!svg) {
  3308. svg = me.svg = Ext.get(document.createElementNS(me.svgNS, 'svg'));
  3309. me.bodyElement.append(svg);
  3310. }
  3311. return svg;
  3312. },
  3313. getTrackArc: function() {
  3314. var me = this,
  3315. trackArc = me.trackArc;
  3316. if (!trackArc) {
  3317. trackArc = me.trackArc = document.createElementNS(me.svgNS, 'path');
  3318. me.getSvg().append(trackArc, true);
  3319. // Note: Ext.dom.Element.addCls doesn't work on SVG elements,
  3320. // as it simply assigns a class string to el.dom.className,
  3321. // which in case of SVG is no simple string:
  3322. // SVGAnimatedString {baseVal: "x-gauge-track", animVal: "x-gauge-track"}
  3323. trackArc.setAttribute('class', Ext.baseCSSPrefix + 'gauge-track');
  3324. }
  3325. return trackArc;
  3326. },
  3327. getValueArc: function() {
  3328. var me = this,
  3329. valueArc = me.valueArc;
  3330. me.getTrackArc();
  3331. // make sure the track arc is created first for proper draw order
  3332. if (!valueArc) {
  3333. valueArc = me.valueArc = document.createElementNS(me.svgNS, 'path');
  3334. me.getSvg().append(valueArc, true);
  3335. valueArc.setAttribute('class', Ext.baseCSSPrefix + 'gauge-value');
  3336. }
  3337. return valueArc;
  3338. },
  3339. applyNeedle: function(needle, oldNeedle) {
  3340. // Make sure the track and value elements have been already created,
  3341. // so that the needle element renders on top.
  3342. this.getValueArc();
  3343. return Ext.Factory.gaugeNeedle.update(oldNeedle, needle, this, 'createNeedle', 'needleDefaults');
  3344. },
  3345. createNeedle: function(config) {
  3346. return Ext.apply({
  3347. gauge: this
  3348. }, config);
  3349. },
  3350. getDefs: function() {
  3351. var me = this,
  3352. defs = me.defs;
  3353. if (!defs) {
  3354. defs = me.defs = Ext.get(document.createElementNS(me.svgNS, 'defs'));
  3355. me.getSvg().appendChild(defs);
  3356. }
  3357. return defs;
  3358. },
  3359. /**
  3360. * @private
  3361. */
  3362. setGradientSize: function(gradient, x1, y1, x2, y2) {
  3363. gradient.setAttribute('x1', x1);
  3364. gradient.setAttribute('y1', y1);
  3365. gradient.setAttribute('x2', x2);
  3366. gradient.setAttribute('y2', y2);
  3367. },
  3368. /**
  3369. * @private
  3370. */
  3371. resizeGradients: function(size) {
  3372. var me = this,
  3373. trackGradient = me.getTrackGradient(),
  3374. valueGradient = me.getValueGradient(),
  3375. x1 = 0,
  3376. y1 = size.height / 2,
  3377. x2 = size.width,
  3378. y2 = size.height / 2;
  3379. me.setGradientSize(trackGradient.dom, x1, y1, x2, y2);
  3380. me.setGradientSize(valueGradient.dom, x1, y1, x2, y2);
  3381. },
  3382. /**
  3383. * @private
  3384. */
  3385. setGradientStops: function(gradient, stops) {
  3386. var ln = stops.length,
  3387. i, stopCfg, stopEl;
  3388. while (gradient.firstChild) {
  3389. gradient.removeChild(gradient.firstChild);
  3390. }
  3391. for (i = 0; i < ln; i++) {
  3392. stopCfg = stops[i];
  3393. stopEl = document.createElementNS(this.svgNS, 'stop');
  3394. gradient.appendChild(stopEl);
  3395. stopEl.setAttribute('offset', stopCfg.offset);
  3396. stopEl.setAttribute('stop-color', stopCfg.color);
  3397. if ('opacity' in stopCfg) {
  3398. stopEl.setAttribute('stop-opacity', stopCfg.opacity);
  3399. }
  3400. }
  3401. },
  3402. getTrackGradient: function() {
  3403. var me = this,
  3404. trackGradient = me.trackGradient;
  3405. if (!trackGradient) {
  3406. trackGradient = me.trackGradient = Ext.get(document.createElementNS(me.svgNS, 'linearGradient'));
  3407. // Using absolute values for x1, y1, x2, y2 attributes.
  3408. trackGradient.dom.setAttribute('gradientUnits', 'userSpaceOnUse');
  3409. me.getDefs().appendChild(trackGradient);
  3410. Ext.get(trackGradient);
  3411. }
  3412. // assign unique ID
  3413. return trackGradient;
  3414. },
  3415. getValueGradient: function() {
  3416. var me = this,
  3417. valueGradient = me.valueGradient;
  3418. if (!valueGradient) {
  3419. valueGradient = me.valueGradient = Ext.get(document.createElementNS(me.svgNS, 'linearGradient'));
  3420. // Using absolute values for x1, y1, x2, y2 attributes.
  3421. valueGradient.dom.setAttribute('gradientUnits', 'userSpaceOnUse');
  3422. me.getDefs().appendChild(valueGradient);
  3423. Ext.get(valueGradient);
  3424. }
  3425. // assign unique ID
  3426. return valueGradient;
  3427. },
  3428. getArcPoint: function(centerX, centerY, radius, degrees) {
  3429. var radians = degrees / 180 * Math.PI;
  3430. return [
  3431. centerX + radius * Math.cos(radians),
  3432. centerY + radius * Math.sin(radians)
  3433. ];
  3434. },
  3435. isCircle: function(startAngle, endAngle) {
  3436. return Ext.Number.isEqual(Math.abs(endAngle - startAngle), 360, 0.001);
  3437. },
  3438. getArcPath: function(centerX, centerY, innerRadius, outerRadius, startAngle, endAngle, round) {
  3439. var me = this,
  3440. isCircle = me.isCircle(startAngle, endAngle),
  3441. // It's not possible to draw a circle using arcs.
  3442. endAngle = endAngle - 0.01,
  3443. // eslint-disable-line no-redeclare
  3444. innerStartPoint = me.getArcPoint(centerX, centerY, innerRadius, startAngle),
  3445. innerEndPoint = me.getArcPoint(centerX, centerY, innerRadius, endAngle),
  3446. outerStartPoint = me.getArcPoint(centerX, centerY, outerRadius, startAngle),
  3447. outerEndPoint = me.getArcPoint(centerX, centerY, outerRadius, endAngle),
  3448. large = endAngle - startAngle <= 180 ? 0 : 1,
  3449. path = [
  3450. 'M',
  3451. innerStartPoint[0],
  3452. innerStartPoint[1],
  3453. 'A',
  3454. innerRadius,
  3455. innerRadius,
  3456. 0,
  3457. large,
  3458. 1,
  3459. innerEndPoint[0],
  3460. innerEndPoint[1]
  3461. ],
  3462. capRadius = (outerRadius - innerRadius) / 2;
  3463. if (isCircle) {
  3464. path.push('M', outerEndPoint[0], outerEndPoint[1]);
  3465. } else {
  3466. if (round) {
  3467. path.push('A', capRadius, capRadius, 0, 0, 0, outerEndPoint[0], outerEndPoint[1]);
  3468. } else {
  3469. path.push('L', outerEndPoint[0], outerEndPoint[1]);
  3470. }
  3471. }
  3472. path.push('A', outerRadius, outerRadius, 0, large, 0, outerStartPoint[0], outerStartPoint[1]);
  3473. if (round && !isCircle) {
  3474. path.push('A', capRadius, capRadius, 0, 0, 0, innerStartPoint[0], innerStartPoint[1]);
  3475. }
  3476. path.push('Z');
  3477. return path.join(' ');
  3478. },
  3479. resizeHandler: function(size) {
  3480. var me = this,
  3481. svg = me.getSvg();
  3482. svg.setSize(size);
  3483. me.resizeGradients(size);
  3484. me.render();
  3485. },
  3486. /**
  3487. * @private
  3488. * Creates a linear interpolator function that itself has a few methods:
  3489. * - `setDomain(from, to)`
  3490. * - `setRange(from, to)`
  3491. * - `getDomain` - returns the domain as a [from, to] array
  3492. * - `getRange` - returns the range as a [from, to] array
  3493. * @param {Boolean} [rangeCheck=false]
  3494. * Whether to allow out of bounds values for domain and range.
  3495. * @return {Function} The interpolator function:
  3496. * `interpolator(domainValue, isInvert)`.
  3497. * If the `isInvert` parameter is `true`, the start of domain will correspond
  3498. * to the end of range. This is useful, for example, when you want to render
  3499. * increasing domain values counter-clockwise instead of clockwise.
  3500. */
  3501. createInterpolator: function(rangeCheck) {
  3502. var domainStart = 0,
  3503. domainDelta = 1,
  3504. rangeStart = 0,
  3505. rangeEnd = 1,
  3506. interpolator = function(x, invert) {
  3507. var t = 0;
  3508. if (domainDelta) {
  3509. t = (x - domainStart) / domainDelta;
  3510. if (rangeCheck) {
  3511. t = Math.max(0, t);
  3512. t = Math.min(1, t);
  3513. }
  3514. if (invert) {
  3515. t = 1 - t;
  3516. }
  3517. }
  3518. return (1 - t) * rangeStart + t * rangeEnd;
  3519. };
  3520. interpolator.setDomain = function(a, b) {
  3521. domainStart = a;
  3522. domainDelta = b - a;
  3523. return this;
  3524. };
  3525. interpolator.setRange = function(a, b) {
  3526. rangeStart = a;
  3527. rangeEnd = b;
  3528. return this;
  3529. };
  3530. interpolator.getDomain = function() {
  3531. return [
  3532. domainStart,
  3533. domainStart + domainDelta
  3534. ];
  3535. };
  3536. interpolator.getRange = function() {
  3537. return [
  3538. rangeStart,
  3539. rangeEnd
  3540. ];
  3541. };
  3542. return interpolator;
  3543. },
  3544. applyAnimation: function(animation) {
  3545. if (true === animation) {
  3546. animation = {};
  3547. } else if (false === animation) {
  3548. animation = {
  3549. duration: 0
  3550. };
  3551. }
  3552. if (!('duration' in animation)) {
  3553. animation.duration = 1000;
  3554. }
  3555. if (!(animation.easing in this.easings)) {
  3556. animation.easing = 'out';
  3557. }
  3558. return animation;
  3559. },
  3560. updateAnimation: function() {
  3561. this.stopAnimation();
  3562. },
  3563. /**
  3564. * @private
  3565. * @param {Number} from
  3566. * @param {Number} to
  3567. * @param {Number} duration
  3568. * @param {Function} easing
  3569. * @param {Function} fn Function to execute on every frame of animation.
  3570. * The function takes a single parameter - the value in the [from, to]
  3571. * range, interpolated based on current time and easing function.
  3572. * With certain easings, the value may overshoot the range slighly.
  3573. * @param {Object} scope
  3574. */
  3575. animate: function(from, to, duration, easing, fn, scope) {
  3576. var me = this,
  3577. start = Ext.now(),
  3578. interpolator = me.createInterpolator().setRange(from, to);
  3579. function frame() {
  3580. var now = Ext.AnimationQueue.frameStartTime,
  3581. t = Math.min(now - start, duration) / duration,
  3582. value = interpolator(easing(t));
  3583. if (scope) {
  3584. if (typeof fn === 'string') {
  3585. scope[fn].call(scope, value);
  3586. } else {
  3587. fn.call(scope, value);
  3588. }
  3589. } else {
  3590. fn(value);
  3591. }
  3592. if (t >= 1) {
  3593. Ext.AnimationQueue.stop(frame, scope);
  3594. me.fx = null;
  3595. }
  3596. }
  3597. me.stopAnimation();
  3598. Ext.AnimationQueue.start(frame, scope);
  3599. me.fx = {
  3600. frame: frame,
  3601. scope: scope
  3602. };
  3603. },
  3604. /**
  3605. * Stops the current {@link #value} or {@link #angleOffset} animation.
  3606. */
  3607. stopAnimation: function() {
  3608. var me = this;
  3609. if (me.fx) {
  3610. Ext.AnimationQueue.stop(me.fx.frame, me.fx.scope);
  3611. me.fx = null;
  3612. }
  3613. },
  3614. unitCircleExtrema: {
  3615. 0: [
  3616. 1,
  3617. 0
  3618. ],
  3619. 90: [
  3620. 0,
  3621. 1
  3622. ],
  3623. 180: [
  3624. -1,
  3625. 0
  3626. ],
  3627. 270: [
  3628. 0,
  3629. -1
  3630. ],
  3631. 360: [
  3632. 1,
  3633. 0
  3634. ],
  3635. 450: [
  3636. 0,
  3637. 1
  3638. ],
  3639. 540: [
  3640. -1,
  3641. 0
  3642. ],
  3643. 630: [
  3644. 0,
  3645. -1
  3646. ]
  3647. },
  3648. /**
  3649. * @private
  3650. */
  3651. getUnitSectorExtrema: function(startAngle, lengthAngle) {
  3652. var extrema = this.unitCircleExtrema,
  3653. points = [],
  3654. angle;
  3655. for (angle in extrema) {
  3656. if (angle > startAngle && angle < startAngle + lengthAngle) {
  3657. points.push(extrema[angle]);
  3658. }
  3659. }
  3660. return points;
  3661. },
  3662. /**
  3663. * @private
  3664. * Given a rect with a known width and height, find the maximum radius of the donut
  3665. * sector that can fit into it, as well as the center point of such a sector.
  3666. * The end and start angles of the sector are also known, as well as the relationship
  3667. * between the inner and outer radii.
  3668. */
  3669. fitSectorInRect: function(width, height, startAngle, lengthAngle, ratio) {
  3670. if (Ext.Number.isEqual(lengthAngle, 360, 0.001)) {
  3671. return {
  3672. cx: width / 2,
  3673. cy: height / 2,
  3674. radius: Math.min(width, height) / 2,
  3675. region: new Ext.util.Region(0, width, height, 0)
  3676. };
  3677. }
  3678. // eslint-disable-next-line vars-on-top
  3679. var me = this,
  3680. points, xx, yy, minX, maxX, minY, maxY,
  3681. cache = me.fitSectorInRectCache,
  3682. sameAngles = cache.startAngle === startAngle && cache.lengthAngle === lengthAngle;
  3683. if (sameAngles) {
  3684. minX = cache.minX;
  3685. maxX = cache.maxX;
  3686. minY = cache.minY;
  3687. maxY = cache.maxY;
  3688. } else {
  3689. points = me.getUnitSectorExtrema(startAngle, lengthAngle).concat([
  3690. // start angle outer radius point
  3691. me.getArcPoint(0, 0, 1, startAngle),
  3692. // start angle inner radius point
  3693. me.getArcPoint(0, 0, ratio, startAngle),
  3694. // end angle outer radius point
  3695. me.getArcPoint(0, 0, 1, startAngle + lengthAngle),
  3696. // end angle inner radius point
  3697. me.getArcPoint(0, 0, ratio, startAngle + lengthAngle)
  3698. ]);
  3699. xx = points.map(function(point) {
  3700. return point[0];
  3701. });
  3702. yy = points.map(function(point) {
  3703. return point[1];
  3704. });
  3705. // The bounding box of a unit sector with the given properties.
  3706. minX = Math.min.apply(null, xx);
  3707. maxX = Math.max.apply(null, xx);
  3708. minY = Math.min.apply(null, yy);
  3709. maxY = Math.max.apply(null, yy);
  3710. cache.startAngle = startAngle;
  3711. cache.lengthAngle = lengthAngle;
  3712. cache.minX = minX;
  3713. cache.maxX = maxX;
  3714. cache.minY = minY;
  3715. cache.maxY = maxY;
  3716. }
  3717. // eslint-disable-next-line vars-on-top, one-var
  3718. var sectorWidth = maxX - minX,
  3719. sectorHeight = maxY - minY,
  3720. scaleX = width / sectorWidth,
  3721. scaleY = height / sectorHeight,
  3722. scale = Math.min(scaleX, scaleY),
  3723. // Region constructor takes: top, right, bottom, left.
  3724. sectorRegion = new Ext.util.Region(minY * scale, maxX * scale, maxY * scale, minX * scale),
  3725. rectRegion = new Ext.util.Region(0, width, height, 0),
  3726. alignedRegion = sectorRegion.alignTo({
  3727. align: 'c-c',
  3728. // align sector region's center to rect region's center
  3729. target: rectRegion
  3730. }),
  3731. dx = alignedRegion.left - minX * scale,
  3732. dy = alignedRegion.top - minY * scale;
  3733. return {
  3734. cx: dx,
  3735. cy: dy,
  3736. radius: scale,
  3737. region: alignedRegion
  3738. };
  3739. },
  3740. /**
  3741. * @private
  3742. */
  3743. fitSectorInPaddedRect: function(width, height, padding, startAngle, lengthAngle, ratio) {
  3744. var result = this.fitSectorInRect(width - padding * 2, height - padding * 2, startAngle, lengthAngle, ratio);
  3745. result.cx += padding;
  3746. result.cy += padding;
  3747. result.region.translateBy(padding, padding);
  3748. return result;
  3749. },
  3750. /**
  3751. * @private
  3752. */
  3753. normalizeAngle: function(angle) {
  3754. return (angle % 360 + 360) % 360;
  3755. },
  3756. render: function() {
  3757. if (!this.size) {
  3758. return;
  3759. }
  3760. // eslint-disable-next-line vars-on-top
  3761. var me = this,
  3762. textOffset = me.getTextOffset(),
  3763. trackArc = me.getTrackArc(),
  3764. valueArc = me.getValueArc(),
  3765. needle = me.getNeedle(),
  3766. clockwise = me.getClockwise(),
  3767. value = me.fxValue,
  3768. angleOffset = me.fxAngleOffset,
  3769. trackLength = me.getTrackLength(),
  3770. width = me.size.width,
  3771. height = me.size.height,
  3772. paddingFn = me.getPadding(),
  3773. padding = paddingFn(Math.min(width, height)),
  3774. // in the range of [0, 360)
  3775. trackStart = me.normalizeAngle(me.getTrackStart() + angleOffset),
  3776. // in the range of (0, 720)
  3777. trackEnd = trackStart + trackLength,
  3778. valueLength = me.interpolator(value),
  3779. trackStyle = me.getTrackStyle(),
  3780. valueStyle = me.getValueStyle(),
  3781. sector = me.fitSectorInPaddedRect(width, height, padding, trackStart, trackLength, trackStyle.innerRadius.ratio),
  3782. cx = sector.cx,
  3783. cy = sector.cy,
  3784. radius = sector.radius,
  3785. trackInnerRadius = Math.max(0, trackStyle.innerRadius(radius)),
  3786. trackOuterRadius = Math.max(0, trackStyle.outerRadius(radius)),
  3787. valueInnerRadius = Math.max(0, valueStyle.innerRadius(radius)),
  3788. valueOuterRadius = Math.max(0, valueStyle.outerRadius(radius)),
  3789. trackPath = me.getArcPath(cx, cy, trackInnerRadius, trackOuterRadius, trackStart, trackEnd, trackStyle.round),
  3790. valuePath = me.getArcPath(cx, cy, valueInnerRadius, valueOuterRadius, clockwise ? trackStart : trackEnd - valueLength, clockwise ? trackStart + valueLength : trackEnd, valueStyle.round);
  3791. me.centerText(cx + textOffset.dx, cy + textOffset.dy, sector.region, trackInnerRadius, trackOuterRadius);
  3792. trackArc.setAttribute('d', trackPath);
  3793. valueArc.setAttribute('d', valuePath);
  3794. if (needle) {
  3795. needle.setRadius(radius);
  3796. needle.setTransform(cx, cy, -90 + trackStart + valueLength);
  3797. }
  3798. me.fireEvent('render', me);
  3799. }
  3800. });
  3801. Ext.define('Ext.ux.gauge.needle.Arrow', {
  3802. extend: 'Ext.ux.gauge.needle.Abstract',
  3803. alias: 'gauge.needle.arrow',
  3804. config: {
  3805. path: function(ir, or) {
  3806. return or - ir > 30 ? "M0," + (ir + 5) + " L-4," + ir + " L-4," + (ir + 10) + " L-1," + (ir + 15) + " L-1," + (or - 7) + " L-5," + (or - 10) + " L0," + or + " L5," + (or - 10) + " L1," + (or - 7) + " L1," + (ir + 15) + " L4," + (ir + 10) + " L4," + ir + " Z" : '';
  3807. }
  3808. }
  3809. });
  3810. Ext.define('Ext.ux.gauge.needle.Diamond', {
  3811. extend: 'Ext.ux.gauge.needle.Abstract',
  3812. alias: 'gauge.needle.diamond',
  3813. config: {
  3814. path: function(ir, or) {
  3815. return or - ir > 10 ? 'M0,' + ir + ' L-4,' + (ir + 5) + ' L0,' + or + ' L4,' + (ir + 5) + ' Z' : '';
  3816. }
  3817. }
  3818. });
  3819. Ext.define('Ext.ux.gauge.needle.Rectangle', {
  3820. extend: 'Ext.ux.gauge.needle.Abstract',
  3821. alias: 'gauge.needle.rectangle',
  3822. config: {
  3823. path: function(ir, or) {
  3824. return or - ir > 10 ? "M-2," + ir + " L2," + ir + " L2," + or + " L-2," + or + " Z" : '';
  3825. }
  3826. }
  3827. });
  3828. Ext.define('Ext.ux.gauge.needle.Spike', {
  3829. extend: 'Ext.ux.gauge.needle.Abstract',
  3830. alias: 'gauge.needle.spike',
  3831. config: {
  3832. path: function(ir, or) {
  3833. return or - ir > 10 ? "M0," + (ir + 5) + " L-4," + ir + " L0," + or + " L4," + ir + " Z" : '';
  3834. }
  3835. }
  3836. });
  3837. Ext.define('Ext.ux.gauge.needle.Wedge', {
  3838. extend: 'Ext.ux.gauge.needle.Abstract',
  3839. alias: 'gauge.needle.wedge',
  3840. config: {
  3841. path: function(ir, or) {
  3842. return or - ir > 10 ? "M-4," + ir + " L0," + or + " L4," + ir + " Z" : '';
  3843. }
  3844. }
  3845. });
  3846. /**
  3847. * A ratings picker based on `Ext.Gadget`.
  3848. *
  3849. * @example
  3850. * Ext.create({
  3851. * xtype: 'rating',
  3852. * renderTo: Ext.getBody(),
  3853. * listeners: {
  3854. * change: function (picker, value) {
  3855. * console.log('Rating ' + value);
  3856. * }
  3857. * }
  3858. * });
  3859. */
  3860. Ext.define('Ext.ux.rating.Picker', {
  3861. extend: 'Ext.Gadget',
  3862. xtype: 'rating',
  3863. focusable: true,
  3864. /*
  3865. * The "cachedConfig" block is basically the same as "config" except that these
  3866. * values are applied specially to the first instance of the class. After processing
  3867. * these configs, the resulting values are stored on the class `prototype` and the
  3868. * template DOM element also reflects these default values.
  3869. */
  3870. cachedConfig: {
  3871. /**
  3872. * @cfg {String} [family]
  3873. * The CSS `font-family` to use for displaying the `{@link #glyphs}`.
  3874. */
  3875. family: 'monospace',
  3876. /**
  3877. * @cfg {String/String[]/Number[]} [glyphs]
  3878. * Either a string containing the two glyph characters, or an array of two strings
  3879. * containing the individual glyph characters or an array of two numbers with the
  3880. * character codes for the individual glyphs.
  3881. *
  3882. * For example:
  3883. *
  3884. * @example
  3885. * Ext.create({
  3886. * xtype: 'rating',
  3887. * renderTo: Ext.getBody(),
  3888. * glyphs: [ 9671, 9670 ], // '◇◆',
  3889. * listeners: {
  3890. * change: function (picker, value) {
  3891. * console.log('Rating ' + value);
  3892. * }
  3893. * }
  3894. * });
  3895. */
  3896. glyphs: '☆★',
  3897. /**
  3898. * @cfg {Number} [minimum=1]
  3899. * The minimum allowed `{@link #value}` (rating).
  3900. */
  3901. minimum: 1,
  3902. /**
  3903. * @cfg {Number} [limit]
  3904. * The maximum allowed `{@link #value}` (rating).
  3905. */
  3906. limit: 5,
  3907. /**
  3908. * @cfg {String/Object} [overStyle]
  3909. * Optional styles to apply to the rating glyphs when `{@link #trackOver}` is
  3910. * enabled.
  3911. */
  3912. overStyle: null,
  3913. /**
  3914. * @cfg {Number} [rounding=1]
  3915. * The rounding to apply to values. Common choices are 0.5 (for half-steps) or
  3916. * 0.25 (for quarter steps).
  3917. */
  3918. rounding: 1,
  3919. /**
  3920. * @cfg {String} [scale="125%"]
  3921. * The CSS `font-size` to apply to the glyphs. This value defaults to 125% because
  3922. * glyphs in the stock font tend to be too small. When using specially designed
  3923. * "icon fonts" you may want to set this to 100%.
  3924. */
  3925. scale: '125%',
  3926. /**
  3927. * @cfg {String/Object} [selectedStyle]
  3928. * Optional styles to apply to the rating value glyphs.
  3929. */
  3930. selectedStyle: null,
  3931. /**
  3932. * @cfg {Object/String/String[]/Ext.XTemplate/Function} tip
  3933. * A template or a function that produces the tooltip text. The `Object`, `String`
  3934. * and `String[]` forms are converted to an `Ext.XTemplate`. If a function is given,
  3935. * it will be called with an object parameter and should return the tooltip text.
  3936. * The object contains these properties:
  3937. *
  3938. * - component: The rating component requesting the tooltip.
  3939. * - tracking: The current value under the mouse cursor.
  3940. * - trackOver: The value of the `{@link #trackOver}` config.
  3941. * - value: The current value.
  3942. *
  3943. * Templates can use these properties to generate the proper text.
  3944. */
  3945. tip: null,
  3946. /**
  3947. * @cfg {Boolean} [trackOver=true]
  3948. * Determines if mouse movements should temporarily update the displayed value.
  3949. * The actual `value` is only updated on `click` but this rather acts as the
  3950. * "preview" of the value prior to click.
  3951. */
  3952. trackOver: true,
  3953. /**
  3954. * @cfg {Number} value
  3955. * The rating value. This value is bounded by `minimum` and `limit` and is also
  3956. * adjusted by the `rounding`.
  3957. */
  3958. value: null,
  3959. //---------------------------------------------------------------------
  3960. // Private configs
  3961. /**
  3962. * @cfg {String} tooltipText
  3963. * The current tooltip text. This value is set into the DOM by the updater (hence
  3964. * only when it changes). This is intended for use by the tip manager
  3965. * (`{@link Ext.tip.QuickTipManager}`). Developers should never need to set this
  3966. * config since it is handled by virtue of setting other configs (such as the
  3967. * {@link #tooltip} or the {@link #value}.).
  3968. * @private
  3969. */
  3970. tooltipText: null,
  3971. /**
  3972. * @cfg {Number} trackingValue
  3973. * This config is used to when `trackOver` is `true` and represents the tracked
  3974. * value. This config is maintained by our `mousemove` handler. This should not
  3975. * need to be set directly by user code.
  3976. * @private
  3977. */
  3978. trackingValue: null
  3979. },
  3980. config: {
  3981. /**
  3982. * @cfg {Boolean/Object} [animate=false]
  3983. * Specifies an animation to use when changing the `{@link #value}`. When setting
  3984. * this config, it is probably best to set `{@link #trackOver}` to `false`.
  3985. */
  3986. animate: null
  3987. },
  3988. // This object describes our element tree from the root.
  3989. element: {
  3990. cls: 'u' + Ext.baseCSSPrefix + 'rating-picker',
  3991. // Since we are replacing the entire "element" tree, we have to assign this
  3992. // "reference" as would our base class.
  3993. reference: 'element',
  3994. children: [
  3995. {
  3996. reference: 'innerEl',
  3997. cls: 'u' + Ext.baseCSSPrefix + 'rating-picker-inner',
  3998. listeners: {
  3999. click: 'onClick',
  4000. mousemove: 'onMouseMove',
  4001. mouseenter: 'onMouseEnter',
  4002. mouseleave: 'onMouseLeave'
  4003. },
  4004. children: [
  4005. {
  4006. reference: 'valueEl',
  4007. cls: 'u' + Ext.baseCSSPrefix + 'rating-picker-value'
  4008. },
  4009. {
  4010. reference: 'trackerEl',
  4011. cls: 'u' + Ext.baseCSSPrefix + 'rating-picker-tracker'
  4012. }
  4013. ]
  4014. }
  4015. ]
  4016. },
  4017. // Tell the Binding system to default to our "value" config.
  4018. defaultBindProperty: 'value',
  4019. // Enable two-way data binding for the "value" config.
  4020. twoWayBindable: 'value',
  4021. overCls: 'u' + Ext.baseCSSPrefix + 'rating-picker-over',
  4022. trackOverCls: 'u' + Ext.baseCSSPrefix + 'rating-picker-track-over',
  4023. //-------------------------------------------------------------------------
  4024. // Config Appliers
  4025. applyGlyphs: function(value) {
  4026. if (typeof value === 'string') {
  4027. //<debug>
  4028. if (value.length !== 2) {
  4029. Ext.raise('Expected 2 characters for "glyphs" not "' + value + '".');
  4030. }
  4031. //</debug>
  4032. value = [
  4033. value.charAt(0),
  4034. value.charAt(1)
  4035. ];
  4036. } else if (typeof value[0] === 'number') {
  4037. value = [
  4038. String.fromCharCode(value[0]),
  4039. String.fromCharCode(value[1])
  4040. ];
  4041. }
  4042. return value;
  4043. },
  4044. applyOverStyle: function(style) {
  4045. this.trackerEl.applyStyles(style);
  4046. },
  4047. applySelectedStyle: function(style) {
  4048. this.valueEl.applyStyles(style);
  4049. },
  4050. applyTip: function(tip) {
  4051. if (tip && typeof tip !== 'function') {
  4052. if (!tip.isTemplate) {
  4053. tip = new Ext.XTemplate(tip);
  4054. }
  4055. tip = tip.apply.bind(tip);
  4056. }
  4057. return tip;
  4058. },
  4059. applyTrackingValue: function(value) {
  4060. return this.applyValue(value);
  4061. },
  4062. // same rounding as normal value
  4063. applyValue: function(v) {
  4064. var rounding, limit, min;
  4065. if (v !== null) {
  4066. rounding = this.getRounding();
  4067. limit = this.getLimit();
  4068. min = this.getMinimum();
  4069. v = Math.round(Math.round(v / rounding) * rounding * 1000) / 1000;
  4070. v = (v < min) ? min : (v > limit ? limit : v);
  4071. }
  4072. return v;
  4073. },
  4074. //-------------------------------------------------------------------------
  4075. // Event Handlers
  4076. onClick: function(event) {
  4077. var value = this.valueFromEvent(event);
  4078. this.setValue(value);
  4079. },
  4080. onMouseEnter: function() {
  4081. this.element.addCls(this.overCls);
  4082. },
  4083. onMouseLeave: function() {
  4084. this.element.removeCls(this.overCls);
  4085. },
  4086. onMouseMove: function(event) {
  4087. var value = this.valueFromEvent(event);
  4088. this.setTrackingValue(value);
  4089. },
  4090. //-------------------------------------------------------------------------
  4091. // Config Updaters
  4092. updateFamily: function(family) {
  4093. this.element.setStyle('fontFamily', "'" + family + "'");
  4094. },
  4095. updateGlyphs: function() {
  4096. this.refreshGlyphs();
  4097. },
  4098. updateLimit: function() {
  4099. this.refreshGlyphs();
  4100. },
  4101. updateScale: function(size) {
  4102. this.element.setStyle('fontSize', size);
  4103. },
  4104. updateTip: function() {
  4105. this.refreshTip();
  4106. },
  4107. updateTooltipText: function(text) {
  4108. this.setTooltip(text);
  4109. },
  4110. // modern only (replaced by classic override)
  4111. updateTrackingValue: function(value) {
  4112. var me = this,
  4113. trackerEl = me.trackerEl,
  4114. newWidth = me.valueToPercent(value);
  4115. trackerEl.setStyle('width', newWidth);
  4116. me.refreshTip();
  4117. },
  4118. updateTrackOver: function(trackOver) {
  4119. this.element.toggleCls(this.trackOverCls, trackOver);
  4120. },
  4121. updateValue: function(value, oldValue) {
  4122. var me = this,
  4123. animate = me.getAnimate(),
  4124. valueEl = me.valueEl,
  4125. newWidth = me.valueToPercent(value),
  4126. column, record;
  4127. if (me.isConfiguring || !animate) {
  4128. valueEl.setStyle('width', newWidth);
  4129. } else {
  4130. valueEl.stopAnimation();
  4131. valueEl.animate(Ext.merge({
  4132. from: {
  4133. width: me.valueToPercent(oldValue)
  4134. },
  4135. to: {
  4136. width: newWidth
  4137. }
  4138. }, animate));
  4139. }
  4140. me.refreshTip();
  4141. if (!me.isConfiguring) {
  4142. // Since we are (re)configured many times as we are used in a grid cell, we
  4143. // avoid firing the change event unless there are listeners.
  4144. if (me.hasListeners.change) {
  4145. me.fireEvent('change', me, value, oldValue);
  4146. }
  4147. column = me.getWidgetColumn && me.getWidgetColumn();
  4148. record = column && me.getWidgetRecord && me.getWidgetRecord();
  4149. if (record && column.dataIndex) {
  4150. // When used in a widgetcolumn, we should update the backing field. The
  4151. // linkages will be cleared as we are being recycled, so this will only
  4152. // reach this line when we are properly attached to a record and the
  4153. // change is coming from the user (or a call to setValue).
  4154. record.set(column.dataIndex, value);
  4155. }
  4156. }
  4157. },
  4158. //-------------------------------------------------------------------------
  4159. // Config System Optimizations
  4160. //
  4161. // These are to deal with configs that combine to determine what should be
  4162. // rendered in the DOM. For example, "glyphs" and "limit" must both be known
  4163. // to render the proper text nodes. The "tip" and "value" likewise are
  4164. // used to update the tooltipText.
  4165. //
  4166. // To avoid multiple updates to the DOM (one for each config), we simply mark
  4167. // the rendering as invalid and post-process these flags on the tail of any
  4168. // bulk updates.
  4169. afterCachedConfig: function() {
  4170. // Now that we are done setting up the initial values we need to refresh the
  4171. // DOM before we allow Ext.Widget's implementation to cloneNode on it.
  4172. this.refresh();
  4173. return this.callParent(arguments);
  4174. },
  4175. initConfig: function(instanceConfig) {
  4176. this.isConfiguring = true;
  4177. this.callParent([
  4178. instanceConfig
  4179. ]);
  4180. // The firstInstance will already have refreshed the DOM (in afterCacheConfig)
  4181. // but all instances beyond the first need to refresh if they have custom values
  4182. // for one or more configs that affect the DOM (such as "glyphs" and "limit").
  4183. this.refresh();
  4184. },
  4185. setConfig: function() {
  4186. var me = this;
  4187. // Since we could be updating multiple configs, save any updates that need
  4188. // multiple values for afterwards.
  4189. me.isReconfiguring = true;
  4190. me.callParent(arguments);
  4191. me.isReconfiguring = false;
  4192. // Now that all new values are set, we can refresh the DOM.
  4193. me.refresh();
  4194. return me;
  4195. },
  4196. //-------------------------------------------------------------------------
  4197. privates: {
  4198. /**
  4199. * This method returns the DOM text node into which glyphs are placed.
  4200. * @param {HTMLElement} dom The DOM node parent of the text node.
  4201. * @return {HTMLElement} The text node.
  4202. * @private
  4203. */
  4204. getGlyphTextNode: function(dom) {
  4205. var node = dom.lastChild;
  4206. // We want all our text nodes to be at the end of the child list, most
  4207. // especially the text node on the innerEl. That text node affects the
  4208. // default left/right position of our absolutely positioned child divs
  4209. // (trackerEl and valueEl).
  4210. if (!node || node.nodeType !== 3) {
  4211. node = dom.ownerDocument.createTextNode('');
  4212. dom.appendChild(node);
  4213. }
  4214. return node;
  4215. },
  4216. getTooltipData: function() {
  4217. var me = this;
  4218. return {
  4219. component: me,
  4220. tracking: me.getTrackingValue(),
  4221. trackOver: me.getTrackOver(),
  4222. value: me.getValue()
  4223. };
  4224. },
  4225. /**
  4226. * Forcibly refreshes both glyph and tooltip rendering.
  4227. * @private
  4228. */
  4229. refresh: function() {
  4230. var me = this;
  4231. if (me.invalidGlyphs) {
  4232. me.refreshGlyphs(true);
  4233. }
  4234. if (me.invalidTip) {
  4235. me.refreshTip(true);
  4236. }
  4237. },
  4238. /**
  4239. * Refreshes the glyph text rendering unless we are currently performing a
  4240. * bulk config change (initConfig or setConfig).
  4241. * @param {Boolean} now Pass `true` to force the refresh to happen now.
  4242. * @private
  4243. */
  4244. refreshGlyphs: function(now) {
  4245. var me = this,
  4246. later = !now && (me.isConfiguring || me.isReconfiguring),
  4247. el, glyphs, limit, on, off, trackerEl, valueEl;
  4248. if (!later) {
  4249. el = me.getGlyphTextNode(me.innerEl.dom);
  4250. valueEl = me.getGlyphTextNode(me.valueEl.dom);
  4251. trackerEl = me.getGlyphTextNode(me.trackerEl.dom);
  4252. glyphs = me.getGlyphs();
  4253. limit = me.getLimit();
  4254. for (on = off = ''; limit--; ) {
  4255. off += glyphs[0];
  4256. on += glyphs[1];
  4257. }
  4258. el.nodeValue = off;
  4259. valueEl.nodeValue = on;
  4260. trackerEl.nodeValue = on;
  4261. }
  4262. me.invalidGlyphs = later;
  4263. },
  4264. /**
  4265. * Refreshes the tooltip text rendering unless we are currently performing a
  4266. * bulk config change (initConfig or setConfig).
  4267. * @param {Boolean} now Pass `true` to force the refresh to happen now.
  4268. * @private
  4269. */
  4270. refreshTip: function(now) {
  4271. var me = this,
  4272. later = !now && (me.isConfiguring || me.isReconfiguring),
  4273. data, text, tooltip;
  4274. if (!later) {
  4275. tooltip = me.getTip();
  4276. if (tooltip) {
  4277. data = me.getTooltipData();
  4278. text = tooltip(data);
  4279. me.setTooltipText(text);
  4280. }
  4281. }
  4282. me.invalidTip = later;
  4283. },
  4284. /**
  4285. * Convert the coordinates of the given `Event` into a rating value.
  4286. * @param {Ext.event.Event} event The event.
  4287. * @return {Number} The rating based on the given event coordinates.
  4288. * @private
  4289. */
  4290. valueFromEvent: function(event) {
  4291. var me = this,
  4292. el = me.innerEl,
  4293. ex = event.getX(),
  4294. rounding = me.getRounding(),
  4295. cx = el.getX(),
  4296. x = ex - cx,
  4297. w = el.getWidth(),
  4298. limit = me.getLimit(),
  4299. v;
  4300. if (me.getInherited().rtl) {
  4301. x = w - x;
  4302. }
  4303. v = x / w * limit;
  4304. // We have to round up here so that the area we are over is considered
  4305. // the value.
  4306. v = Math.ceil(v / rounding) * rounding;
  4307. return v;
  4308. },
  4309. /**
  4310. * Convert the given rating into a width percentage.
  4311. * @param {Number} value The rating value to convert.
  4312. * @return {String} The width percentage to represent the given value.
  4313. * @private
  4314. */
  4315. valueToPercent: function(value) {
  4316. value = (value / this.getLimit()) * 100;
  4317. return value + '%';
  4318. }
  4319. }
  4320. });
  4321. /**
  4322. * @private
  4323. */
  4324. Ext.define('Ext.ux.colorpick.Selection', {
  4325. mixinId: 'colorselection',
  4326. config: {
  4327. /**
  4328. * @cfg {"hex6"/"hex8"/"#hex6"/"#hex8"/"HEX6"/"HEX8"/"#HEX6"/"#HEX8"} [format=hex6]
  4329. * The color format to for the `value` config. The `value` can be set using any
  4330. * supported format or named color, but the stored value will always be in this
  4331. * format.
  4332. *
  4333. * Supported formats are:
  4334. *
  4335. * - hex6 - For example "ffaa00" (Note: does not preserve transparency).
  4336. * - hex8 - For eaxmple "ffaa00ff" - the last 2 digits represent transparency
  4337. * - #hex6 - For example "#ffaa00" (same as "hex6" but with a leading "#").
  4338. * - #hex8 - For example "#ffaa00ff" (same as "hex8" but with a leading "#").
  4339. * - HEX6 - Same as "hex6" but upper case.
  4340. * - HEX8 - Same as "hex8" but upper case.
  4341. * - #HEX6 - Same as "#hex6" but upper case.
  4342. * - #HEX8 - Same as "#hex8" but upper case.
  4343. */
  4344. format: 'hex6',
  4345. /**
  4346. * @cfg {String} [value=FF0000]
  4347. * The initial color to highlight; see {@link #format} for supported formats.
  4348. */
  4349. value: 'FF0000',
  4350. /**
  4351. * @cfg {Object} color
  4352. * This config property is used internally by the UI to maintain the full color.
  4353. * Changes to this config are automatically reflected in `value` and vise-versa.
  4354. * Setting `value` can, however, cause the alpha to be dropped if the new value
  4355. * does not contain an alpha component.
  4356. * @private
  4357. */
  4358. color: null,
  4359. previousColor: null,
  4360. /**
  4361. * @cfg {String} [alphaDecimalFormat=#.##]
  4362. * The format used by {@link Ext.util.Format#number} to format the alpha channel's
  4363. * value.
  4364. * @since 7.0.0
  4365. */
  4366. alphaDecimalFormat: '#.##'
  4367. },
  4368. applyColor: function(color) {
  4369. var c = color;
  4370. if (Ext.isString(c)) {
  4371. c = Ext.ux.colorpick.ColorUtils.parseColor(color, this.getAlphaDecimalFormat());
  4372. }
  4373. return c;
  4374. },
  4375. applyValue: function(color) {
  4376. // Transform whatever incoming color we get to the proper format
  4377. // eslint-disable-next-line max-len
  4378. var c = Ext.ux.colorpick.ColorUtils.parseColor(color || '#000000', this.getAlphaDecimalFormat());
  4379. return this.formatColor(c);
  4380. },
  4381. formatColor: function(color) {
  4382. return Ext.ux.colorpick.ColorUtils.formats[this.getFormat()](color);
  4383. },
  4384. updateColor: function(color) {
  4385. var me = this;
  4386. // If the "color" is changed (via internal changes in the UI), update "value" as
  4387. // well. Since these are always tracking each other, we guard against the case
  4388. // where we are being updated *because* "value" is being set.
  4389. if (!me.syncing) {
  4390. me.syncing = true;
  4391. me.setValue(me.formatColor(color));
  4392. me.syncing = false;
  4393. }
  4394. },
  4395. updateValue: function(value, oldValue) {
  4396. var me = this;
  4397. // If the "value" is changed, update "color" as well. Since these are always
  4398. // tracking each other, we guard against the case where we are being updated
  4399. // *because* "color" is being set.
  4400. if (!me.syncing) {
  4401. me.syncing = true;
  4402. me.setColor(value);
  4403. me.syncing = false;
  4404. }
  4405. this.fireEvent('change', me, value, oldValue);
  4406. }
  4407. });
  4408. /**
  4409. * @private
  4410. */
  4411. Ext.define('Ext.ux.colorpick.ColorUtils', function(ColorUtils) {
  4412. return {
  4413. singleton: true,
  4414. constructor: function() {
  4415. ColorUtils = this;
  4416. },
  4417. backgroundTpl: 'background: {rgba};',
  4418. setBackground: function(el, color) {
  4419. var tpl, data, bgStyle;
  4420. if (el) {
  4421. tpl = Ext.XTemplate.getTpl(ColorUtils, 'backgroundTpl');
  4422. data = {
  4423. rgba: ColorUtils.getRGBAString(color)
  4424. };
  4425. bgStyle = tpl.apply(data);
  4426. el.applyStyles(bgStyle);
  4427. }
  4428. },
  4429. // parse and format functions under objects that match supported format config
  4430. // values of the color picker; parse() methods recieve the supplied color value
  4431. // as a string (i.e "FFAAAA") and return an object form, just like the one
  4432. // ColorPickerModel vm "selectedColor" uses. That same object form is used as a
  4433. // parameter to the format() methods, where the appropriate string form is expected
  4434. // for the return result
  4435. formats: {
  4436. // "FFAA00"
  4437. HEX6: function(colorO) {
  4438. return ColorUtils.rgb2hex(colorO && colorO.r, colorO && colorO.g, colorO && colorO.b);
  4439. },
  4440. // "FFAA00FF" (last 2 are opacity)
  4441. HEX8: function(colorO) {
  4442. var hex = ColorUtils.rgb2hex(colorO.r, colorO.g, colorO.b),
  4443. opacityHex = Math.round(colorO.a * 255).toString(16);
  4444. if (opacityHex.length < 2) {
  4445. hex += '0';
  4446. }
  4447. hex += opacityHex.toUpperCase();
  4448. return hex;
  4449. },
  4450. rgb: function(color) {
  4451. return ColorUtils.getRGBString(color);
  4452. },
  4453. rgba: function(color) {
  4454. return ColorUtils.getRGBAString(color);
  4455. }
  4456. },
  4457. hexRe: /^#?([0-9a-f]{3,8})/i,
  4458. rgbaAltRe: /rgba\(\s*([\w#\d]+)\s*,\s*([\d\.]+)\s*\)/,
  4459. // eslint-disable-line no-useless-escape
  4460. rgbaRe: /rgba\(\s*([\d\.]+)\s*,\s*([\d\.]+)\s*,\s*([\d\.]+)\s*,\s*([\d\.]+)\s*\)/,
  4461. // eslint-disable-line no-useless-escape
  4462. rgbRe: /rgb\(\s*([\d\.]+)\s*,\s*([\d\.]+)\s*,\s*([\d\.]+)\s*\)/,
  4463. // eslint-disable-line no-useless-escape
  4464. /**
  4465. * Turn a string to a color object. Supports these formats:
  4466. *
  4467. * - "#ABC" (HEX short)
  4468. * - "#ABCDEF" (HEX)
  4469. * - "#ABCDEFDD" (HEX with opacity)
  4470. * - "red" (named colors - see
  4471. * [Web Colors](http://en.wikipedia.org/wiki/Web_colors) for a full list)
  4472. * - "rgba(r,g,b,a)" i.e "rgba(255,0,0,1)" (a == alpha == 0-1)
  4473. * - "rgba(red, 0.4)"
  4474. * - "rgba(#ABC, 0.9)"
  4475. * - "rgba(#ABCDEF, 0.8)"
  4476. *
  4477. * @param {String} color The color string to parse.
  4478. * @param {String} alphaFormat The format of decimal places for the Alpha channel.
  4479. * @return {Object} Object with various color properties.
  4480. * @return {Number} return.r The red component (0-255).
  4481. * @return {Number} return.g The green component (0-255).
  4482. * @return {Number} return.b The blue component (0-255).
  4483. * @return {Number} return.a The red component (0-1).
  4484. * @return {Number} return.h The hue component (0-1).
  4485. * @return {Number} return.s The saturation component (0-1).
  4486. * @return {Number} return.v The value component (0-1).
  4487. */
  4488. parseColor: function(color, alphaFormat) {
  4489. var me = this,
  4490. rgb, match, ret, hsv;
  4491. if (!color) {
  4492. return null;
  4493. }
  4494. rgb = me.colorMap[color];
  4495. if (rgb) {
  4496. ret = {
  4497. r: rgb[0],
  4498. g: rgb[1],
  4499. b: rgb[2],
  4500. a: 1
  4501. };
  4502. } else if (color === 'transparent') {
  4503. ret = {
  4504. r: 0,
  4505. g: 0,
  4506. b: 0,
  4507. a: 0
  4508. };
  4509. } else {
  4510. match = me.hexRe.exec(color);
  4511. if (match) {
  4512. match = match[1];
  4513. // the captured hex
  4514. switch (match.length) {
  4515. default:
  4516. return null;
  4517. case 3:
  4518. ret = {
  4519. // double the number (e.g. 6 - > 66, a -> aa) and convert to decimal
  4520. r: parseInt(match[0] + match[0], 16),
  4521. g: parseInt(match[1] + match[1], 16),
  4522. b: parseInt(match[2] + match[2], 16),
  4523. a: 1
  4524. };
  4525. break;
  4526. case 6:
  4527. case 8:
  4528. ret = {
  4529. r: parseInt(match.substr(0, 2), 16),
  4530. g: parseInt(match.substr(2, 2), 16),
  4531. b: parseInt(match.substr(4, 2), 16),
  4532. a: parseInt(match.substr(6, 2) || 'ff', 16) / 255
  4533. };
  4534. break;
  4535. }
  4536. } else {
  4537. match = me.rgbaRe.exec(color);
  4538. if (match) {
  4539. // proper css => rgba(r,g,b,a)
  4540. ret = {
  4541. r: parseFloat(match[1]),
  4542. g: parseFloat(match[2]),
  4543. b: parseFloat(match[3]),
  4544. a: parseFloat(match[4])
  4545. };
  4546. } else {
  4547. match = me.rgbaAltRe.exec(color);
  4548. if (match) {
  4549. // scss shorthands =?
  4550. // rgba(red, 0.4),rgba(#222, 0.9), rgba(#444433, 0.8)
  4551. ret = me.parseColor(match[1]);
  4552. // we have HSV filled in, so poke on "a" and we're done
  4553. ret.a = parseFloat(match[2]);
  4554. return ret;
  4555. }
  4556. match = me.rgbRe.exec(color);
  4557. if (match) {
  4558. ret = {
  4559. r: parseFloat(match[1]),
  4560. g: parseFloat(match[2]),
  4561. b: parseFloat(match[3]),
  4562. a: 1
  4563. };
  4564. } else {
  4565. return null;
  4566. }
  4567. }
  4568. }
  4569. }
  4570. // format alpha channel
  4571. if (alphaFormat) {
  4572. ret.a = Ext.util.Format.number(ret.a, alphaFormat);
  4573. }
  4574. hsv = this.rgb2hsv(ret.r, ret.g, ret.b);
  4575. return Ext.apply(ret, hsv);
  4576. },
  4577. isValid: function(color) {
  4578. return ColorUtils.parseColor(color) !== null;
  4579. },
  4580. /**
  4581. *
  4582. * @param rgba
  4583. * @return {String}
  4584. */
  4585. getRGBAString: function(rgba) {
  4586. // set default value if selected color is set to null
  4587. rgba = rgba === null ? {
  4588. r: 0,
  4589. g: 0,
  4590. b: 0,
  4591. h: 1,
  4592. s: 1,
  4593. v: 1,
  4594. a: "1"
  4595. } : rgba;
  4596. return "rgba(" + rgba.r + "," + rgba.g + "," + rgba.b + "," + rgba.a + ")";
  4597. },
  4598. /**
  4599. * Returns a rgb css string whith this color (without the alpha channel)
  4600. * @param rgb
  4601. * @return {String}
  4602. */
  4603. getRGBString: function(rgb) {
  4604. return "rgb(" + rgb.r + "," + rgb.g + "," + rgb.b + ")";
  4605. },
  4606. /**
  4607. * Following standard math to convert from hsl to rgb
  4608. * Check out wikipedia page for more information on how this works
  4609. * h => [0,1]
  4610. * s,l => [0,1]
  4611. * @param h
  4612. * @param s
  4613. * @param v
  4614. * @return {Object} An object with "r", "g" and "b" color properties.
  4615. */
  4616. hsv2rgb: function(h, s, v) {
  4617. var c, hprime, x, rgb, m;
  4618. h = h > 1 ? 1 : h;
  4619. s = s > 1 ? 1 : s;
  4620. v = v > 1 ? 1 : v;
  4621. h = h === undefined ? 1 : h;
  4622. h = h * 360;
  4623. if (h === 360) {
  4624. h = 0;
  4625. }
  4626. c = v * s;
  4627. hprime = h / 60;
  4628. x = c * (1 - Math.abs(hprime % 2 - 1));
  4629. rgb = [
  4630. 0,
  4631. 0,
  4632. 0
  4633. ];
  4634. switch (Math.floor(hprime)) {
  4635. case 0:
  4636. rgb = [
  4637. c,
  4638. x,
  4639. 0
  4640. ];
  4641. break;
  4642. case 1:
  4643. rgb = [
  4644. x,
  4645. c,
  4646. 0
  4647. ];
  4648. break;
  4649. case 2:
  4650. rgb = [
  4651. 0,
  4652. c,
  4653. x
  4654. ];
  4655. break;
  4656. case 3:
  4657. rgb = [
  4658. 0,
  4659. x,
  4660. c
  4661. ];
  4662. break;
  4663. case 4:
  4664. rgb = [
  4665. x,
  4666. 0,
  4667. c
  4668. ];
  4669. break;
  4670. case 5:
  4671. rgb = [
  4672. c,
  4673. 0,
  4674. x
  4675. ];
  4676. break;
  4677. default:
  4678. //<debug>
  4679. console.error("unknown color " + h + ' ' + s + " " + v);
  4680. //</debug>
  4681. break;
  4682. }
  4683. m = v - c;
  4684. rgb[0] += m;
  4685. rgb[1] += m;
  4686. rgb[2] += m;
  4687. rgb[0] = Math.round(rgb[0] * 255);
  4688. rgb[1] = Math.round(rgb[1] * 255);
  4689. rgb[2] = Math.round(rgb[2] * 255);
  4690. return {
  4691. r: rgb[0],
  4692. g: rgb[1],
  4693. b: rgb[2]
  4694. };
  4695. },
  4696. /**
  4697. * http://en.wikipedia.org/wiki/HSL_and_HSV
  4698. * @param {Number} r The red component (0-255).
  4699. * @param {Number} g The green component (0-255).
  4700. * @param {Number} b The blue component (0-255).
  4701. * @return {Object} An object with "h", "s" and "v" color properties.
  4702. */
  4703. rgb2hsv: function(r, g, b) {
  4704. var M, m, c, hprime, h, v, s;
  4705. r = r / 255;
  4706. g = g / 255;
  4707. b = b / 255;
  4708. M = Math.max(r, g, b);
  4709. m = Math.min(r, g, b);
  4710. c = M - m;
  4711. hprime = 0;
  4712. if (c !== 0) {
  4713. if (M === r) {
  4714. hprime = ((g - b) / c) % 6;
  4715. } else if (M === g) {
  4716. hprime = ((b - r) / c) + 2;
  4717. } else if (M === b) {
  4718. hprime = ((r - g) / c) + 4;
  4719. }
  4720. }
  4721. h = hprime * 60;
  4722. if (h === 360) {
  4723. h = 0;
  4724. }
  4725. v = M;
  4726. s = 0;
  4727. if (c !== 0) {
  4728. s = c / v;
  4729. }
  4730. h = h / 360;
  4731. if (h < 0) {
  4732. h = h + 1;
  4733. }
  4734. return {
  4735. h: h,
  4736. s: s,
  4737. v: v
  4738. };
  4739. },
  4740. /**
  4741. *
  4742. * @param r
  4743. * @param g
  4744. * @param b
  4745. * @return {String}
  4746. */
  4747. rgb2hex: function(r, g, b) {
  4748. r = r === null ? r : r.toString(16);
  4749. g = g === null ? g : g.toString(16);
  4750. b = b === null ? b : b.toString(16);
  4751. if (r === null || r.length < 2) {
  4752. r = '0' + r || '0';
  4753. }
  4754. if (g === null || g.length < 2) {
  4755. g = '0' + g || '0';
  4756. }
  4757. if (b === null || b.length < 2) {
  4758. b = '0' + b || '0';
  4759. }
  4760. if (r === null || r.length > 2) {
  4761. r = 'ff';
  4762. }
  4763. if (g === null || g.length > 2) {
  4764. g = 'ff';
  4765. }
  4766. if (b === null || b.length > 2) {
  4767. b = 'ff';
  4768. }
  4769. return (r + g + b).toUpperCase();
  4770. },
  4771. colorMap: {
  4772. aliceblue: [
  4773. 240,
  4774. 248,
  4775. 255
  4776. ],
  4777. antiquewhite: [
  4778. 250,
  4779. 235,
  4780. 215
  4781. ],
  4782. aqua: [
  4783. 0,
  4784. 255,
  4785. 255
  4786. ],
  4787. aquamarine: [
  4788. 127,
  4789. 255,
  4790. 212
  4791. ],
  4792. azure: [
  4793. 240,
  4794. 255,
  4795. 255
  4796. ],
  4797. beige: [
  4798. 245,
  4799. 245,
  4800. 220
  4801. ],
  4802. bisque: [
  4803. 255,
  4804. 228,
  4805. 196
  4806. ],
  4807. black: [
  4808. 0,
  4809. 0,
  4810. 0
  4811. ],
  4812. blanchedalmond: [
  4813. 255,
  4814. 235,
  4815. 205
  4816. ],
  4817. blue: [
  4818. 0,
  4819. 0,
  4820. 255
  4821. ],
  4822. blueviolet: [
  4823. 138,
  4824. 43,
  4825. 226
  4826. ],
  4827. brown: [
  4828. 165,
  4829. 42,
  4830. 42
  4831. ],
  4832. burlywood: [
  4833. 222,
  4834. 184,
  4835. 135
  4836. ],
  4837. cadetblue: [
  4838. 95,
  4839. 158,
  4840. 160
  4841. ],
  4842. chartreuse: [
  4843. 127,
  4844. 255,
  4845. 0
  4846. ],
  4847. chocolate: [
  4848. 210,
  4849. 105,
  4850. 30
  4851. ],
  4852. coral: [
  4853. 255,
  4854. 127,
  4855. 80
  4856. ],
  4857. cornflowerblue: [
  4858. 100,
  4859. 149,
  4860. 237
  4861. ],
  4862. cornsilk: [
  4863. 255,
  4864. 248,
  4865. 220
  4866. ],
  4867. crimson: [
  4868. 220,
  4869. 20,
  4870. 60
  4871. ],
  4872. cyan: [
  4873. 0,
  4874. 255,
  4875. 255
  4876. ],
  4877. darkblue: [
  4878. 0,
  4879. 0,
  4880. 139
  4881. ],
  4882. darkcyan: [
  4883. 0,
  4884. 139,
  4885. 139
  4886. ],
  4887. darkgoldenrod: [
  4888. 184,
  4889. 132,
  4890. 11
  4891. ],
  4892. darkgray: [
  4893. 169,
  4894. 169,
  4895. 169
  4896. ],
  4897. darkgreen: [
  4898. 0,
  4899. 100,
  4900. 0
  4901. ],
  4902. darkgrey: [
  4903. 169,
  4904. 169,
  4905. 169
  4906. ],
  4907. darkkhaki: [
  4908. 189,
  4909. 183,
  4910. 107
  4911. ],
  4912. darkmagenta: [
  4913. 139,
  4914. 0,
  4915. 139
  4916. ],
  4917. darkolivegreen: [
  4918. 85,
  4919. 107,
  4920. 47
  4921. ],
  4922. darkorange: [
  4923. 255,
  4924. 140,
  4925. 0
  4926. ],
  4927. darkorchid: [
  4928. 153,
  4929. 50,
  4930. 204
  4931. ],
  4932. darkred: [
  4933. 139,
  4934. 0,
  4935. 0
  4936. ],
  4937. darksalmon: [
  4938. 233,
  4939. 150,
  4940. 122
  4941. ],
  4942. darkseagreen: [
  4943. 143,
  4944. 188,
  4945. 143
  4946. ],
  4947. darkslateblue: [
  4948. 72,
  4949. 61,
  4950. 139
  4951. ],
  4952. darkslategray: [
  4953. 47,
  4954. 79,
  4955. 79
  4956. ],
  4957. darkslategrey: [
  4958. 47,
  4959. 79,
  4960. 79
  4961. ],
  4962. darkturquoise: [
  4963. 0,
  4964. 206,
  4965. 209
  4966. ],
  4967. darkviolet: [
  4968. 148,
  4969. 0,
  4970. 211
  4971. ],
  4972. deeppink: [
  4973. 255,
  4974. 20,
  4975. 147
  4976. ],
  4977. deepskyblue: [
  4978. 0,
  4979. 191,
  4980. 255
  4981. ],
  4982. dimgray: [
  4983. 105,
  4984. 105,
  4985. 105
  4986. ],
  4987. dimgrey: [
  4988. 105,
  4989. 105,
  4990. 105
  4991. ],
  4992. dodgerblue: [
  4993. 30,
  4994. 144,
  4995. 255
  4996. ],
  4997. firebrick: [
  4998. 178,
  4999. 34,
  5000. 34
  5001. ],
  5002. floralwhite: [
  5003. 255,
  5004. 255,
  5005. 240
  5006. ],
  5007. forestgreen: [
  5008. 34,
  5009. 139,
  5010. 34
  5011. ],
  5012. fuchsia: [
  5013. 255,
  5014. 0,
  5015. 255
  5016. ],
  5017. gainsboro: [
  5018. 220,
  5019. 220,
  5020. 220
  5021. ],
  5022. ghostwhite: [
  5023. 248,
  5024. 248,
  5025. 255
  5026. ],
  5027. gold: [
  5028. 255,
  5029. 215,
  5030. 0
  5031. ],
  5032. goldenrod: [
  5033. 218,
  5034. 165,
  5035. 32
  5036. ],
  5037. gray: [
  5038. 128,
  5039. 128,
  5040. 128
  5041. ],
  5042. green: [
  5043. 0,
  5044. 128,
  5045. 0
  5046. ],
  5047. greenyellow: [
  5048. 173,
  5049. 255,
  5050. 47
  5051. ],
  5052. grey: [
  5053. 128,
  5054. 128,
  5055. 128
  5056. ],
  5057. honeydew: [
  5058. 240,
  5059. 255,
  5060. 240
  5061. ],
  5062. hotpink: [
  5063. 255,
  5064. 105,
  5065. 180
  5066. ],
  5067. indianred: [
  5068. 205,
  5069. 92,
  5070. 92
  5071. ],
  5072. indigo: [
  5073. 75,
  5074. 0,
  5075. 130
  5076. ],
  5077. ivory: [
  5078. 255,
  5079. 255,
  5080. 240
  5081. ],
  5082. khaki: [
  5083. 240,
  5084. 230,
  5085. 140
  5086. ],
  5087. lavender: [
  5088. 230,
  5089. 230,
  5090. 250
  5091. ],
  5092. lavenderblush: [
  5093. 255,
  5094. 240,
  5095. 245
  5096. ],
  5097. lawngreen: [
  5098. 124,
  5099. 252,
  5100. 0
  5101. ],
  5102. lemonchiffon: [
  5103. 255,
  5104. 250,
  5105. 205
  5106. ],
  5107. lightblue: [
  5108. 173,
  5109. 216,
  5110. 230
  5111. ],
  5112. lightcoral: [
  5113. 240,
  5114. 128,
  5115. 128
  5116. ],
  5117. lightcyan: [
  5118. 224,
  5119. 255,
  5120. 255
  5121. ],
  5122. lightgoldenrodyellow: [
  5123. 250,
  5124. 250,
  5125. 210
  5126. ],
  5127. lightgray: [
  5128. 211,
  5129. 211,
  5130. 211
  5131. ],
  5132. lightgreen: [
  5133. 144,
  5134. 238,
  5135. 144
  5136. ],
  5137. lightgrey: [
  5138. 211,
  5139. 211,
  5140. 211
  5141. ],
  5142. lightpink: [
  5143. 255,
  5144. 182,
  5145. 193
  5146. ],
  5147. lightsalmon: [
  5148. 255,
  5149. 160,
  5150. 122
  5151. ],
  5152. lightseagreen: [
  5153. 32,
  5154. 178,
  5155. 170
  5156. ],
  5157. lightskyblue: [
  5158. 135,
  5159. 206,
  5160. 250
  5161. ],
  5162. lightslategray: [
  5163. 119,
  5164. 136,
  5165. 153
  5166. ],
  5167. lightslategrey: [
  5168. 119,
  5169. 136,
  5170. 153
  5171. ],
  5172. lightsteelblue: [
  5173. 176,
  5174. 196,
  5175. 222
  5176. ],
  5177. lightyellow: [
  5178. 255,
  5179. 255,
  5180. 224
  5181. ],
  5182. lime: [
  5183. 0,
  5184. 255,
  5185. 0
  5186. ],
  5187. limegreen: [
  5188. 50,
  5189. 205,
  5190. 50
  5191. ],
  5192. linen: [
  5193. 250,
  5194. 240,
  5195. 230
  5196. ],
  5197. magenta: [
  5198. 255,
  5199. 0,
  5200. 255
  5201. ],
  5202. maroon: [
  5203. 128,
  5204. 0,
  5205. 0
  5206. ],
  5207. mediumaquamarine: [
  5208. 102,
  5209. 205,
  5210. 170
  5211. ],
  5212. mediumblue: [
  5213. 0,
  5214. 0,
  5215. 205
  5216. ],
  5217. mediumorchid: [
  5218. 186,
  5219. 85,
  5220. 211
  5221. ],
  5222. mediumpurple: [
  5223. 147,
  5224. 112,
  5225. 219
  5226. ],
  5227. mediumseagreen: [
  5228. 60,
  5229. 179,
  5230. 113
  5231. ],
  5232. mediumslateblue: [
  5233. 123,
  5234. 104,
  5235. 238
  5236. ],
  5237. mediumspringgreen: [
  5238. 0,
  5239. 250,
  5240. 154
  5241. ],
  5242. mediumturquoise: [
  5243. 72,
  5244. 209,
  5245. 204
  5246. ],
  5247. mediumvioletred: [
  5248. 199,
  5249. 21,
  5250. 133
  5251. ],
  5252. midnightblue: [
  5253. 25,
  5254. 25,
  5255. 112
  5256. ],
  5257. mintcream: [
  5258. 245,
  5259. 255,
  5260. 250
  5261. ],
  5262. mistyrose: [
  5263. 255,
  5264. 228,
  5265. 225
  5266. ],
  5267. moccasin: [
  5268. 255,
  5269. 228,
  5270. 181
  5271. ],
  5272. navajowhite: [
  5273. 255,
  5274. 222,
  5275. 173
  5276. ],
  5277. navy: [
  5278. 0,
  5279. 0,
  5280. 128
  5281. ],
  5282. oldlace: [
  5283. 253,
  5284. 245,
  5285. 230
  5286. ],
  5287. olive: [
  5288. 128,
  5289. 128,
  5290. 0
  5291. ],
  5292. olivedrab: [
  5293. 107,
  5294. 142,
  5295. 35
  5296. ],
  5297. orange: [
  5298. 255,
  5299. 165,
  5300. 0
  5301. ],
  5302. orangered: [
  5303. 255,
  5304. 69,
  5305. 0
  5306. ],
  5307. orchid: [
  5308. 218,
  5309. 112,
  5310. 214
  5311. ],
  5312. palegoldenrod: [
  5313. 238,
  5314. 232,
  5315. 170
  5316. ],
  5317. palegreen: [
  5318. 152,
  5319. 251,
  5320. 152
  5321. ],
  5322. paleturquoise: [
  5323. 175,
  5324. 238,
  5325. 238
  5326. ],
  5327. palevioletred: [
  5328. 219,
  5329. 112,
  5330. 147
  5331. ],
  5332. papayawhip: [
  5333. 255,
  5334. 239,
  5335. 213
  5336. ],
  5337. peachpuff: [
  5338. 255,
  5339. 218,
  5340. 185
  5341. ],
  5342. peru: [
  5343. 205,
  5344. 133,
  5345. 63
  5346. ],
  5347. pink: [
  5348. 255,
  5349. 192,
  5350. 203
  5351. ],
  5352. plum: [
  5353. 221,
  5354. 160,
  5355. 203
  5356. ],
  5357. powderblue: [
  5358. 176,
  5359. 224,
  5360. 230
  5361. ],
  5362. purple: [
  5363. 128,
  5364. 0,
  5365. 128
  5366. ],
  5367. red: [
  5368. 255,
  5369. 0,
  5370. 0
  5371. ],
  5372. rosybrown: [
  5373. 188,
  5374. 143,
  5375. 143
  5376. ],
  5377. royalblue: [
  5378. 65,
  5379. 105,
  5380. 225
  5381. ],
  5382. saddlebrown: [
  5383. 139,
  5384. 69,
  5385. 19
  5386. ],
  5387. salmon: [
  5388. 250,
  5389. 128,
  5390. 114
  5391. ],
  5392. sandybrown: [
  5393. 244,
  5394. 164,
  5395. 96
  5396. ],
  5397. seagreen: [
  5398. 46,
  5399. 139,
  5400. 87
  5401. ],
  5402. seashell: [
  5403. 255,
  5404. 245,
  5405. 238
  5406. ],
  5407. sienna: [
  5408. 160,
  5409. 82,
  5410. 45
  5411. ],
  5412. silver: [
  5413. 192,
  5414. 192,
  5415. 192
  5416. ],
  5417. skyblue: [
  5418. 135,
  5419. 206,
  5420. 235
  5421. ],
  5422. slateblue: [
  5423. 106,
  5424. 90,
  5425. 205
  5426. ],
  5427. slategray: [
  5428. 119,
  5429. 128,
  5430. 144
  5431. ],
  5432. slategrey: [
  5433. 119,
  5434. 128,
  5435. 144
  5436. ],
  5437. snow: [
  5438. 255,
  5439. 255,
  5440. 250
  5441. ],
  5442. springgreen: [
  5443. 0,
  5444. 255,
  5445. 127
  5446. ],
  5447. steelblue: [
  5448. 70,
  5449. 130,
  5450. 180
  5451. ],
  5452. tan: [
  5453. 210,
  5454. 180,
  5455. 140
  5456. ],
  5457. teal: [
  5458. 0,
  5459. 128,
  5460. 128
  5461. ],
  5462. thistle: [
  5463. 216,
  5464. 191,
  5465. 216
  5466. ],
  5467. tomato: [
  5468. 255,
  5469. 99,
  5470. 71
  5471. ],
  5472. turquoise: [
  5473. 64,
  5474. 224,
  5475. 208
  5476. ],
  5477. violet: [
  5478. 238,
  5479. 130,
  5480. 238
  5481. ],
  5482. wheat: [
  5483. 245,
  5484. 222,
  5485. 179
  5486. ],
  5487. white: [
  5488. 255,
  5489. 255,
  5490. 255
  5491. ],
  5492. whitesmoke: [
  5493. 245,
  5494. 245,
  5495. 245
  5496. ],
  5497. yellow: [
  5498. 255,
  5499. 255,
  5500. 0
  5501. ],
  5502. yellowgreen: [
  5503. 154,
  5504. 205,
  5505. 5
  5506. ]
  5507. }
  5508. };
  5509. }, function(ColorUtils) {
  5510. var formats = ColorUtils.formats,
  5511. lowerized = {};
  5512. formats['#HEX6'] = function(color) {
  5513. return '#' + formats.HEX6(color);
  5514. };
  5515. formats['#HEX8'] = function(color) {
  5516. return '#' + formats.HEX8(color);
  5517. };
  5518. Ext.Object.each(formats, function(name, fn) {
  5519. lowerized[name.toLowerCase()] = function(color) {
  5520. var ret = fn(color);
  5521. return ret.toLowerCase();
  5522. };
  5523. });
  5524. Ext.apply(formats, lowerized);
  5525. });
  5526. /**
  5527. * @private
  5528. */
  5529. Ext.define('Ext.ux.colorpick.ColorMapController', {
  5530. extend: 'Ext.app.ViewController',
  5531. alias: 'controller.colorpickercolormapcontroller',
  5532. requires: [
  5533. 'Ext.ux.colorpick.ColorUtils'
  5534. ],
  5535. init: function() {
  5536. var me = this,
  5537. colorMap = me.getView();
  5538. // event handlers
  5539. me.mon(colorMap.bodyElement, {
  5540. mousedown: me.onMouseDown,
  5541. mouseup: me.onMouseUp,
  5542. mousemove: me.onMouseMove,
  5543. scope: me
  5544. });
  5545. },
  5546. // Fires when handle is dragged; propagates "handledrag" event on the ColorMap
  5547. // with parameters "percentX" and "percentY", both 0-1, representing the handle
  5548. // position on the color map, relative to the container
  5549. onHandleDrag: function(componentDragger, e) {
  5550. var me = this,
  5551. container = me.getView(),
  5552. // the Color Map
  5553. dragHandle = container.down('#dragHandle').element,
  5554. x = dragHandle.getX() - container.element.getX(),
  5555. y = dragHandle.getY() - container.element.getY(),
  5556. containerEl = container.bodyElement,
  5557. containerWidth = containerEl.getWidth(),
  5558. containerHeight = containerEl.getHeight(),
  5559. xRatio = x / containerWidth,
  5560. yRatio = y / containerHeight;
  5561. // Adjust x/y ratios for dragger always being 1 pixel from the edge on the right
  5562. if (xRatio > 0.99) {
  5563. xRatio = 1;
  5564. }
  5565. if (yRatio > 0.99) {
  5566. yRatio = 1;
  5567. }
  5568. // Adjust x/y ratios for dragger always being 0 pixel from the edge on the left
  5569. if (xRatio < 0) {
  5570. xRatio = 0;
  5571. }
  5572. if (yRatio < 0) {
  5573. yRatio = 0;
  5574. }
  5575. container.fireEvent('handledrag', xRatio, yRatio);
  5576. },
  5577. // Whenever we mousedown over the colormap area
  5578. onMouseDown: function(e) {
  5579. var me = this;
  5580. me.onMapClick(e);
  5581. me.onHandleDrag();
  5582. me.isDragging = true;
  5583. },
  5584. onMouseUp: function(e) {
  5585. var me = this;
  5586. me.onMapClick(e);
  5587. me.onHandleDrag();
  5588. me.isDragging = false;
  5589. },
  5590. onMouseMove: function(e) {
  5591. var me = this;
  5592. if (me.isDragging) {
  5593. me.onMapClick(e);
  5594. me.onHandleDrag();
  5595. }
  5596. },
  5597. // Whenever the map is clicked (but not the drag handle) we need to position
  5598. // the drag handle to the point of click
  5599. onMapClick: function(e) {
  5600. var me = this,
  5601. container = me.getView(),
  5602. // the Color Map
  5603. dragHandle = container.down('#dragHandle'),
  5604. cXY = container.element.getXY(),
  5605. eXY = e.getXY(),
  5606. left, top;
  5607. left = eXY[0] - cXY[0];
  5608. top = eXY[1] - cXY[1];
  5609. dragHandle.element.setStyle({
  5610. left: left + 'px',
  5611. top: top + 'px'
  5612. });
  5613. e.preventDefault();
  5614. me.onHandleDrag();
  5615. },
  5616. // Whenever the underlying binding data is changed we need to
  5617. // update position of the dragger.
  5618. onColorBindingChanged: function(selectedColor) {
  5619. var me = this,
  5620. vm = me.getViewModel(),
  5621. rgba = vm.get('selectedColor'),
  5622. hsv,
  5623. container = me.getView(),
  5624. // the Color Map
  5625. dragHandle = container.down('#dragHandle'),
  5626. containerEl = container.bodyElement,
  5627. containerWidth = containerEl.getWidth(),
  5628. containerHeight = containerEl.getHeight(),
  5629. xRatio, yRatio, left, top;
  5630. // set default value if selected color is set to null
  5631. rgba = rgba === null ? {
  5632. r: 0,
  5633. g: 0,
  5634. b: 0,
  5635. h: 1,
  5636. s: 1,
  5637. v: 1,
  5638. a: "1"
  5639. } : rgba;
  5640. // Color map selection really only depends on saturation and value of the color
  5641. hsv = Ext.ux.colorpick.ColorUtils.rgb2hsv(rgba.r, rgba.g, rgba.b);
  5642. // x-axis of color map with value 0-1 translates to saturation
  5643. xRatio = hsv.s;
  5644. left = containerWidth * xRatio;
  5645. // y-axis of color map with value 0-1 translates to reverse of "value"
  5646. yRatio = 1 - hsv.v;
  5647. top = containerHeight * yRatio;
  5648. // Position dragger
  5649. dragHandle.element.setStyle({
  5650. left: left + 'px',
  5651. top: top + 'px'
  5652. });
  5653. },
  5654. // Whenever only Hue changes we can update the
  5655. // background color of the color map
  5656. // Param "hue" has value of 0-1
  5657. onHueBindingChanged: function(hue) {
  5658. var me = this,
  5659. fullColorRGB, hex;
  5660. fullColorRGB = Ext.ux.colorpick.ColorUtils.hsv2rgb(hue, 1, 1);
  5661. hex = Ext.ux.colorpick.ColorUtils.rgb2hex(fullColorRGB.r, fullColorRGB.g, fullColorRGB.b);
  5662. me.getView().element.applyStyles({
  5663. 'background-color': '#' + hex
  5664. });
  5665. }
  5666. });
  5667. /**
  5668. * The main colorful square for selecting color shades by dragging around the
  5669. * little circle.
  5670. * @private
  5671. */
  5672. Ext.define('Ext.ux.colorpick.ColorMap', {
  5673. extend: 'Ext.container.Container',
  5674. alias: 'widget.colorpickercolormap',
  5675. controller: 'colorpickercolormapcontroller',
  5676. requires: [
  5677. 'Ext.ux.colorpick.ColorMapController'
  5678. ],
  5679. cls: Ext.baseCSSPrefix + 'colorpicker-colormap',
  5680. // This is the drag "circle"; note it's 1x1 in size to allow full
  5681. // travel around the color map; the inner div has the bigger image
  5682. items: [
  5683. {
  5684. xtype: 'component',
  5685. cls: Ext.baseCSSPrefix + 'colorpicker-colormap-draghandle-container',
  5686. itemId: 'dragHandle',
  5687. width: 1,
  5688. height: 1,
  5689. style: {
  5690. position: 'relative'
  5691. },
  5692. html: '<div class="' + Ext.baseCSSPrefix + 'colorpicker-colormap-draghandle"></div>'
  5693. }
  5694. ],
  5695. listeners: {
  5696. colorbindingchanged: {
  5697. fn: 'onColorBindingChanged',
  5698. scope: 'controller'
  5699. },
  5700. huebindingchanged: {
  5701. fn: 'onHueBindingChanged',
  5702. scope: 'controller'
  5703. }
  5704. },
  5705. afterRender: function() {
  5706. var me = this,
  5707. src = me.mapGradientUrl,
  5708. el = me.el;
  5709. me.callParent();
  5710. if (!src) {
  5711. // We do this trick to allow the Sass to calculate resource image path for
  5712. // our package and pick up the proper image URL here.
  5713. src = el.getStyle('background-image');
  5714. src = src.substring(4, src.length - 1);
  5715. // strip off outer "url(...)"
  5716. // In IE8 this path will have quotes around it
  5717. if (src.indexOf('"') === 0) {
  5718. src = src.substring(1, src.length - 1);
  5719. }
  5720. // Then remember it on our prototype for any subsequent instances.
  5721. Ext.ux.colorpick.ColorMap.prototype.mapGradientUrl = src;
  5722. }
  5723. // Now clear that style because it will conflict with the background-color
  5724. el.setStyle('background-image', 'none');
  5725. // Create the image with transparent PNG with black and white gradient shades;
  5726. // it blends with the background color (which changes with hue selection). This
  5727. // must be an IMG in order to properly stretch to fit.
  5728. el = me.bodyElement;
  5729. el.createChild({
  5730. tag: 'img',
  5731. cls: Ext.baseCSSPrefix + 'colorpicker-colormap-blender',
  5732. src: src
  5733. });
  5734. },
  5735. // Called via data binding whenever selectedColor changes; fires "colorbindingchanged"
  5736. setPosition: function(data) {
  5737. var me = this,
  5738. dragHandle = me.down('#dragHandle');
  5739. // User actively dragging? Skip event
  5740. if (dragHandle.isDragging) {
  5741. return;
  5742. }
  5743. this.fireEvent('colorbindingchanged', data);
  5744. },
  5745. // Called via data binding whenever selectedColor.h changes; fires "huebindingchanged" event
  5746. setHue: function(hue) {
  5747. var me = this;
  5748. me.fireEvent('huebindingchanged', hue);
  5749. }
  5750. });
  5751. /**
  5752. * View Model that holds the "selectedColor" of the color picker container.
  5753. */
  5754. Ext.define('Ext.ux.colorpick.SelectorModel', {
  5755. extend: 'Ext.app.ViewModel',
  5756. alias: 'viewmodel.colorpick-selectormodel',
  5757. requires: [
  5758. 'Ext.ux.colorpick.ColorUtils'
  5759. ],
  5760. data: {
  5761. selectedColor: {
  5762. r: 255,
  5763. // red
  5764. g: 255,
  5765. // green
  5766. b: 255,
  5767. // blue
  5768. h: 0,
  5769. // hue,
  5770. s: 1,
  5771. // saturation
  5772. v: 1,
  5773. // value
  5774. a: 1
  5775. },
  5776. // alpha (opacity)
  5777. previousColor: {
  5778. r: 0,
  5779. // red
  5780. g: 0,
  5781. // green
  5782. b: 0,
  5783. // blue
  5784. h: 0,
  5785. // hue,
  5786. s: 1,
  5787. // saturation
  5788. v: 1,
  5789. // value
  5790. a: 1
  5791. }
  5792. },
  5793. // alpha (opacity)
  5794. formulas: {
  5795. // Hexadecimal representation of the color
  5796. hex: {
  5797. get: function(get) {
  5798. var r = get('selectedColor.r') === null ? get('selectedColor.r') : get('selectedColor.r').toString(16),
  5799. g = get('selectedColor.g') === null ? get('selectedColor.g') : get('selectedColor.g').toString(16),
  5800. b = get('selectedColor.b') === null ? get('selectedColor.b') : get('selectedColor.b').toString(16),
  5801. result;
  5802. result = Ext.ux.colorpick.ColorUtils.rgb2hex(r, g, b);
  5803. return '#' + result;
  5804. },
  5805. set: function(hex) {
  5806. var rgb;
  5807. if (!Ext.isEmpty(hex)) {
  5808. rgb = Ext.ux.colorpick.ColorUtils.parseColor(hex);
  5809. this.changeRGB(rgb);
  5810. }
  5811. }
  5812. },
  5813. // "R" in "RGB"
  5814. red: {
  5815. get: function(get) {
  5816. return get('selectedColor.r');
  5817. },
  5818. set: function(r) {
  5819. this.changeRGB({
  5820. r: r
  5821. });
  5822. }
  5823. },
  5824. // "G" in "RGB"
  5825. green: {
  5826. get: function(get) {
  5827. return get('selectedColor.g');
  5828. },
  5829. set: function(g) {
  5830. this.changeRGB({
  5831. g: g
  5832. });
  5833. }
  5834. },
  5835. // "B" in "RGB"
  5836. blue: {
  5837. get: function(get) {
  5838. return get('selectedColor.b');
  5839. },
  5840. set: function(b) {
  5841. this.changeRGB({
  5842. b: b
  5843. });
  5844. }
  5845. },
  5846. // "H" in HSV
  5847. hue: {
  5848. get: function(get) {
  5849. return get('selectedColor.h') * 360;
  5850. },
  5851. set: function(hue) {
  5852. this.changeHSV({
  5853. h: hue && hue / 360
  5854. });
  5855. }
  5856. },
  5857. // "S" in HSV
  5858. saturation: {
  5859. get: function(get) {
  5860. return get('selectedColor.s') * 100;
  5861. },
  5862. set: function(saturation) {
  5863. this.changeHSV({
  5864. s: saturation && saturation / 100
  5865. });
  5866. }
  5867. },
  5868. // "V" in HSV
  5869. value: {
  5870. get: function(get) {
  5871. var v = get('selectedColor.v');
  5872. return v * 100;
  5873. },
  5874. set: function(value) {
  5875. this.changeHSV({
  5876. v: value && value / 100
  5877. });
  5878. }
  5879. },
  5880. alpha: {
  5881. get: function(data) {
  5882. var a = data('selectedColor.a');
  5883. return a * 100;
  5884. },
  5885. set: function(alpha) {
  5886. if (alpha !== null) {
  5887. this.set('selectedColor', Ext.applyIf({
  5888. a: alpha / 100
  5889. }, this.data.selectedColor));
  5890. }
  5891. }
  5892. }
  5893. },
  5894. // formulas
  5895. changeHSV: function(hsv) {
  5896. var rgb;
  5897. if (hsv.h !== null && hsv.s !== null && hsv.v !== null) {
  5898. Ext.applyIf(hsv, this.data.selectedColor);
  5899. rgb = Ext.ux.colorpick.ColorUtils.hsv2rgb(hsv.h, hsv.s, hsv.v);
  5900. hsv.r = rgb.r;
  5901. hsv.g = rgb.g;
  5902. hsv.b = rgb.b;
  5903. this.set('selectedColor', hsv);
  5904. }
  5905. },
  5906. changeRGB: function(rgb) {
  5907. var hsv;
  5908. Ext.applyIf(rgb, this.data.selectedColor);
  5909. if (rgb) {
  5910. if (rgb.r !== null && rgb.g !== null && rgb.b !== null) {
  5911. hsv = Ext.ux.colorpick.ColorUtils.rgb2hsv(rgb.r, rgb.g, rgb.b);
  5912. rgb.h = hsv.h;
  5913. rgb.s = hsv.s;
  5914. rgb.v = hsv.v;
  5915. this.set('selectedColor', rgb);
  5916. }
  5917. }
  5918. }
  5919. });
  5920. /**
  5921. * @private
  5922. */
  5923. Ext.define('Ext.ux.colorpick.SelectorController', {
  5924. extend: 'Ext.app.ViewController',
  5925. alias: 'controller.colorpick-selectorcontroller',
  5926. requires: [
  5927. 'Ext.ux.colorpick.ColorUtils'
  5928. ],
  5929. destroy: function() {
  5930. var me = this,
  5931. view = me.getView(),
  5932. childViewModel = view.childViewModel;
  5933. if (childViewModel) {
  5934. childViewModel.destroy();
  5935. view.childViewModel = null;
  5936. }
  5937. me.callParent();
  5938. },
  5939. changeHSV: function(hsv) {
  5940. var view = this.getView(),
  5941. color = view.getColor(),
  5942. rgb;
  5943. // Put in values we are not changing (like A, but also missing HSV values)
  5944. Ext.applyIf(hsv, color);
  5945. // Now that HSV is complete, recalculate RGB and combine them
  5946. rgb = Ext.ux.colorpick.ColorUtils.hsv2rgb(hsv.h, hsv.s, hsv.v);
  5947. Ext.apply(hsv, rgb);
  5948. view.setColor(hsv);
  5949. },
  5950. // Updates Saturation/Value in the model based on ColorMap; params:
  5951. // xPercent - where is the handle relative to the color map width
  5952. // yPercent - where is the handle relative to the color map height
  5953. onColorMapHandleDrag: function(xPercent, yPercent) {
  5954. this.changeHSV({
  5955. s: xPercent,
  5956. v: 1 - yPercent
  5957. });
  5958. },
  5959. // Updates HSV Value in the model and downstream RGB settings
  5960. onValueSliderHandleDrag: function(yPercent) {
  5961. this.changeHSV({
  5962. v: 1 - yPercent
  5963. });
  5964. },
  5965. // Updates HSV Saturation in the model and downstream RGB settings
  5966. onSaturationSliderHandleDrag: function(yPercent) {
  5967. this.changeHSV({
  5968. s: 1 - yPercent
  5969. });
  5970. },
  5971. // Updates Hue in the model and downstream RGB settings
  5972. onHueSliderHandleDrag: function(yPercent) {
  5973. this.changeHSV({
  5974. h: 1 - yPercent
  5975. });
  5976. },
  5977. onAlphaSliderHandleDrag: function(yPercent) {
  5978. var view = this.getView(),
  5979. color = view.getColor(),
  5980. newColor = Ext.applyIf({
  5981. a: 1 - yPercent
  5982. }, color);
  5983. view.setColor(newColor);
  5984. view.el.repaint();
  5985. },
  5986. onPreviousColorSelected: function(comp, color) {
  5987. var view = this.getView();
  5988. view.setColor(color);
  5989. },
  5990. onOK: function() {
  5991. var me = this,
  5992. view = me.getView();
  5993. view.fireEvent('ok', view, view.getValue());
  5994. },
  5995. onCancel: function() {
  5996. this.fireViewEvent('cancel', this.getView());
  5997. },
  5998. onResize: function() {
  5999. var me = this,
  6000. view = me.getView(),
  6001. vm = view.childViewModel,
  6002. refs = me.getReferences(),
  6003. h, s, v, a;
  6004. h = vm.get('hue');
  6005. s = vm.get('saturation');
  6006. v = vm.get('value');
  6007. a = vm.get('alpha');
  6008. // Reposition the colormap's & sliders' drag handles
  6009. refs.colorMap.setPosition(vm.getData());
  6010. refs.hueSlider.setHue(h);
  6011. refs.satSlider.setSaturation(s);
  6012. refs.valueSlider.setValue(v);
  6013. refs.alphaSlider.setAlpha(a);
  6014. }
  6015. });
  6016. /**
  6017. * A basic component that changes background color, with considerations for opacity
  6018. * support (checkered background image and IE8 support).
  6019. */
  6020. Ext.define('Ext.ux.colorpick.ColorPreview', {
  6021. extend: 'Ext.Component',
  6022. alias: 'widget.colorpickercolorpreview',
  6023. requires: [
  6024. 'Ext.util.Format'
  6025. ],
  6026. cls: Ext.baseCSSPrefix + 'colorpreview',
  6027. getTemplate: function() {
  6028. return [
  6029. {
  6030. reference: 'filterElement',
  6031. cls: Ext.baseCSSPrefix + 'colorpreview-filter-el'
  6032. },
  6033. {
  6034. reference: 'btnElement',
  6035. cls: Ext.baseCSSPrefix + 'colorpreview-btn-el',
  6036. tag: 'a'
  6037. }
  6038. ];
  6039. },
  6040. onRender: function() {
  6041. var me = this;
  6042. me.callParent(arguments);
  6043. me.mon(me.btnElement, 'click', me.onClick, me);
  6044. },
  6045. onClick: function(e) {
  6046. e.preventDefault();
  6047. this.fireEvent('click', this, this.color);
  6048. },
  6049. // Called via databinding - update background color whenever ViewModel changes
  6050. setColor: function(color) {
  6051. this.color = color;
  6052. this.applyBgStyle(color);
  6053. },
  6054. applyBgStyle: function(color) {
  6055. var me = this,
  6056. colorUtils = Ext.ux.colorpick.ColorUtils,
  6057. el = me.filterElement,
  6058. rgba;
  6059. rgba = colorUtils.getRGBAString(color);
  6060. el.applyStyles({
  6061. background: rgba
  6062. });
  6063. }
  6064. });
  6065. /**
  6066. * @private
  6067. */
  6068. Ext.define('Ext.ux.colorpick.SliderController', {
  6069. extend: 'Ext.app.ViewController',
  6070. alias: 'controller.colorpick-slidercontroller',
  6071. getDragHandle: function() {
  6072. return this.view.lookupReference('dragHandle');
  6073. },
  6074. getDragContainer: function() {
  6075. return this.view.lookupReference('dragHandleContainer');
  6076. },
  6077. // Fires when handle is dragged; fires "handledrag" event on the slider
  6078. // with parameter "percentY" 0-1, representing the handle position on the slider
  6079. // relative to the height
  6080. onHandleDrag: function(e) {
  6081. var me = this,
  6082. view = me.getView(),
  6083. container = me.getDragContainer(),
  6084. dragHandle = me.getDragHandle(),
  6085. containerEl = container.bodyElement,
  6086. top = containerEl.getY(),
  6087. y = e.getY() - containerEl.getY(),
  6088. containerHeight = containerEl.getHeight(),
  6089. yRatio = y / containerHeight;
  6090. if (y >= 0 && y < containerHeight) {
  6091. dragHandle.element.setY(y + top);
  6092. } else {
  6093. return;
  6094. }
  6095. // Adjust y ratio for dragger always being 1 pixel from the edge on the bottom
  6096. if (yRatio > 0.99) {
  6097. yRatio = 1;
  6098. }
  6099. e.preventDefault();
  6100. view.fireEvent('handledrag', yRatio);
  6101. dragHandle.el.repaint();
  6102. },
  6103. // Whenever we mousedown over the slider area
  6104. onMouseDown: function(e) {
  6105. var me = this,
  6106. dragHandle = me.getDragHandle();
  6107. // position drag handle accordingly
  6108. dragHandle.isDragging = true;
  6109. me.onHandleDrag(e);
  6110. },
  6111. onMouseMove: function(e) {
  6112. var me = this,
  6113. dragHandle = me.getDragHandle();
  6114. if (dragHandle.isDragging) {
  6115. me.onHandleDrag(e);
  6116. }
  6117. },
  6118. onMouseUp: function(e) {
  6119. var me = this,
  6120. dragHandle = me.getDragHandle();
  6121. if (dragHandle.isDragging) {
  6122. me.onHandleDrag(e);
  6123. }
  6124. dragHandle.isDragging = false;
  6125. }
  6126. });
  6127. /**
  6128. * Parent view for the 4 sliders seen on the color picker window.
  6129. * @private
  6130. */
  6131. Ext.define('Ext.ux.colorpick.Slider', {
  6132. extend: 'Ext.container.Container',
  6133. xtype: 'colorpickerslider',
  6134. controller: 'colorpick-slidercontroller',
  6135. afterRender: function() {
  6136. var width, dragCt, dragWidth;
  6137. this.callParent(arguments);
  6138. width = this.getWidth();
  6139. dragCt = this.lookupReference('dragHandleContainer');
  6140. dragWidth = dragCt.getWidth();
  6141. dragCt.el.setStyle('left', ((width - dragWidth) / 4) + 'px');
  6142. },
  6143. baseCls: Ext.baseCSSPrefix + 'colorpicker-slider',
  6144. requires: [
  6145. 'Ext.ux.colorpick.SliderController'
  6146. ],
  6147. referenceHolder: true,
  6148. listeners: {
  6149. element: 'element',
  6150. touchstart: 'onMouseDown',
  6151. touchend: 'onMouseUp',
  6152. touchmove: 'onMouseMove'
  6153. },
  6154. autoSize: false,
  6155. // Container for the drag handle; needed since the slider
  6156. // is of static size, while the parent container positions
  6157. // it in the center; this is what receives the beautiful
  6158. // color gradients for the visual
  6159. items: {
  6160. xtype: 'container',
  6161. cls: Ext.baseCSSPrefix + 'colorpicker-draghandle-container',
  6162. reference: 'dragHandleContainer',
  6163. height: '100%',
  6164. // This is the drag handle; note it's 100%x1 in size to allow full
  6165. // vertical drag travel; the inner div has the bigger image
  6166. items: {
  6167. xtype: 'component',
  6168. cls: Ext.baseCSSPrefix + 'colorpicker-draghandle-outer',
  6169. style: {
  6170. position: 'relative'
  6171. },
  6172. reference: 'dragHandle',
  6173. width: '100%',
  6174. height: 1,
  6175. // draggable: true,
  6176. html: '<div class="' + Ext.baseCSSPrefix + 'colorpicker-draghandle"></div>'
  6177. }
  6178. },
  6179. //<debug>
  6180. // Called via data binding whenever selectedColor.h changes;
  6181. setHue: function() {
  6182. Ext.raise('Must implement setHue() in a child class!');
  6183. },
  6184. //</debug>
  6185. getDragHandle: function() {
  6186. return this.lookupReference('dragHandle');
  6187. },
  6188. getDragContainer: function() {
  6189. return this.lookupReference('dragHandleContainer');
  6190. }
  6191. });
  6192. /**
  6193. * Used for "Alpha" slider.
  6194. * @private
  6195. */
  6196. Ext.define('Ext.ux.colorpick.SliderAlpha', {
  6197. extend: 'Ext.ux.colorpick.Slider',
  6198. alias: 'widget.colorpickerslideralpha',
  6199. cls: Ext.baseCSSPrefix + 'colorpicker-alpha',
  6200. requires: [
  6201. 'Ext.XTemplate'
  6202. ],
  6203. gradientStyleTpl: Ext.create('Ext.XTemplate', // eslint-disable-next-line max-len
  6204. 'background: -moz-linear-gradient(top, rgba({r}, {g}, {b}, 1) 0%, rgba({r}, {g}, {b}, 0) 100%);' + /* FF3.6+ */
  6205. // eslint-disable-next-line max-len
  6206. 'background: -webkit-linear-gradient(top,rgba({r}, {g}, {b}, 1) 0%, rgba({r}, {g}, {b}, 0) 100%);' + /* Chrome10+,Safari5.1+ */
  6207. // eslint-disable-next-line max-len
  6208. 'background: -o-linear-gradient(top, rgba({r}, {g}, {b}, 1) 0%, rgba({r}, {g}, {b}, 0) 100%);' + /* Opera 11.10+ */
  6209. // eslint-disable-next-line max-len
  6210. 'background: -ms-linear-gradient(top, rgba({r}, {g}, {b}, 1) 0%, rgba({r}, {g}, {b}, 0) 100%);' + /* IE10+ */
  6211. // eslint-disable-next-line max-len
  6212. 'background: linear-gradient(to bottom, rgba({r}, {g}, {b}, 1) 0%, rgba({r}, {g}, {b}, 0) 100%);'),
  6213. /* W3C */
  6214. // Called via data binding whenever selectedColor.a changes; param is 0-100
  6215. setAlpha: function(value) {
  6216. var me = this,
  6217. container = me.getDragContainer(),
  6218. dragHandle = me.getDragHandle(),
  6219. containerEl = container.bodyElement,
  6220. containerHeight = containerEl.getHeight(),
  6221. el, top;
  6222. value = Math.max(value, 0);
  6223. value = Math.min(value, 100);
  6224. // User actively dragging? Skip event
  6225. if (dragHandle.isDragging) {
  6226. return;
  6227. }
  6228. // y-axis of slider with value 0-1 translates to reverse of "value"
  6229. top = containerHeight * (1 - (value / 100));
  6230. // Position dragger
  6231. el = dragHandle.element;
  6232. el.setStyle({
  6233. top: top + 'px'
  6234. });
  6235. },
  6236. // Called via data binding whenever selectedColor.h changes; hue param is 0-1
  6237. setColor: function(color) {
  6238. var me = this,
  6239. container = me.getDragContainer(),
  6240. hex, el;
  6241. // set default value if selected color is set to null
  6242. color = color === null ? {
  6243. r: 0,
  6244. g: 0,
  6245. b: 0,
  6246. h: 1,
  6247. s: 1,
  6248. v: 1,
  6249. a: "1"
  6250. } : color;
  6251. // Determine HEX for new hue and set as background based on template
  6252. hex = Ext.ux.colorpick.ColorUtils.rgb2hex(color.r, color.g, color.b);
  6253. el = container.bodyElement;
  6254. el.applyStyles(me.gradientStyleTpl.apply({
  6255. hex: hex,
  6256. r: color.r,
  6257. g: color.g,
  6258. b: color.b
  6259. }));
  6260. }
  6261. });
  6262. /**
  6263. * Used for "Saturation" slider
  6264. * @private
  6265. */
  6266. Ext.define('Ext.ux.colorpick.SliderSaturation', {
  6267. extend: 'Ext.ux.colorpick.Slider',
  6268. alias: 'widget.colorpickerslidersaturation',
  6269. cls: Ext.baseCSSPrefix + 'colorpicker-saturation',
  6270. gradientStyleTpl: Ext.create('Ext.XTemplate', /* FF3.6+ */
  6271. 'background: -mox-linear-gradient(top,#{hex} 0%, #ffffff 100%);' + /* Chrome10+,Safari5.1+ */
  6272. 'background: -webkit-linear-gradient(top, #{hex} 0%,#ffffff 100%);' + /* Opera 11.10+ */
  6273. 'background: -o-linear-gradient(top, #{hex} 0%,#ffffff 100%);' + /* IE10+ */
  6274. 'background: -ms-linear-gradient(top, #{hex} 0%,#ffffff 100%);' + /* W3C */
  6275. 'background: linear-gradient(to bottom, #{hex} 0%,#ffffff 100%);'),
  6276. // Called via data binding whenever selectedColor.s changes; saturation param is 0-100
  6277. setSaturation: function(saturation) {
  6278. var me = this,
  6279. container = me.getDragContainer(),
  6280. dragHandle = me.getDragHandle(),
  6281. containerEl = container.bodyElement,
  6282. containerHeight = containerEl.getHeight(),
  6283. yRatio, top;
  6284. saturation = Math.max(saturation, 0);
  6285. saturation = Math.min(saturation, 100);
  6286. // User actively dragging? Skip event
  6287. if (dragHandle.isDragging) {
  6288. return;
  6289. }
  6290. // y-axis of slider with value 0-1 translates to reverse of "saturation"
  6291. yRatio = 1 - (saturation / 100);
  6292. top = containerHeight * yRatio;
  6293. // Position dragger
  6294. dragHandle.element.setStyle({
  6295. top: top + 'px'
  6296. });
  6297. },
  6298. // Called via data binding whenever selectedColor.h changes; hue param is 0-1
  6299. setHue: function(hue) {
  6300. var me = this,
  6301. container = me.getDragContainer(),
  6302. rgb, hex;
  6303. // Determine HEX for new hue and set as background based on template
  6304. rgb = Ext.ux.colorpick.ColorUtils.hsv2rgb(hue, 1, 1);
  6305. hex = Ext.ux.colorpick.ColorUtils.rgb2hex(rgb.r, rgb.g, rgb.b);
  6306. container.element.applyStyles(me.gradientStyleTpl.apply({
  6307. hex: hex
  6308. }));
  6309. }
  6310. });
  6311. /**
  6312. * Used for "Value" slider.
  6313. * @private
  6314. */
  6315. Ext.define('Ext.ux.colorpick.SliderValue', {
  6316. extend: 'Ext.ux.colorpick.Slider',
  6317. alias: 'widget.colorpickerslidervalue',
  6318. cls: Ext.baseCSSPrefix + 'colorpicker-value',
  6319. requires: [
  6320. 'Ext.XTemplate'
  6321. ],
  6322. gradientStyleTpl: Ext.create('Ext.XTemplate', // eslint-disable-next-line max-len
  6323. 'background: -mox-linear-gradient(top, #{hex} 0%, #000000 100%);' + /* FF3.6+ */
  6324. // eslint-disable-next-line max-len
  6325. 'background: -webkit-linear-gradient(top, #{hex} 0%,#000000 100%);' + /* Chrome10+,Safari5.1+ */
  6326. 'background: -o-linear-gradient(top, #{hex} 0%,#000000 100%);' + /* Opera 11.10+ */
  6327. 'background: -ms-linear-gradient(top, #{hex} 0%,#000000 100%);' + /* IE10+ */
  6328. 'background: linear-gradient(to bottom, #{hex} 0%,#000000 100%);'),
  6329. /* W3C */
  6330. // Called via data binding whenever selectedColor.v changes; value param is 0-100
  6331. setValue: function(value) {
  6332. var me = this,
  6333. container = me.getDragContainer(),
  6334. dragHandle = me.getDragHandle(),
  6335. containerEl = container.bodyElement,
  6336. containerHeight = containerEl.getHeight(),
  6337. yRatio, top;
  6338. value = Math.max(value, 0);
  6339. value = Math.min(value, 100);
  6340. // User actively dragging? Skip event
  6341. if (dragHandle.isDragging) {
  6342. return;
  6343. }
  6344. // y-axis of slider with value 0-1 translates to reverse of "value"
  6345. yRatio = 1 - (value / 100);
  6346. top = containerHeight * yRatio;
  6347. // Position dragger
  6348. dragHandle.element.setStyle({
  6349. top: top + 'px'
  6350. });
  6351. },
  6352. // Called via data binding whenever selectedColor.h changes; hue param is 0-1
  6353. setHue: function(hue) {
  6354. var me = this,
  6355. container = me.getDragContainer(),
  6356. rgb, hex;
  6357. // Too early in the render cycle? Skip event
  6358. if (!me.element) {
  6359. return;
  6360. }
  6361. // Determine HEX for new hue and set as background based on template
  6362. rgb = Ext.ux.colorpick.ColorUtils.hsv2rgb(hue, 1, 1);
  6363. hex = Ext.ux.colorpick.ColorUtils.rgb2hex(rgb.r, rgb.g, rgb.b);
  6364. container.bodyElement.applyStyles(me.gradientStyleTpl.apply({
  6365. hex: hex
  6366. }));
  6367. }
  6368. });
  6369. /**
  6370. * Used for "Hue" slider.
  6371. * @private
  6372. */
  6373. Ext.define('Ext.ux.colorpick.SliderHue', {
  6374. extend: 'Ext.ux.colorpick.Slider',
  6375. alias: 'widget.colorpickersliderhue',
  6376. cls: Ext.baseCSSPrefix + 'colorpicker-hue',
  6377. afterRender: function() {
  6378. var me = this,
  6379. src = me.gradientUrl,
  6380. el = me.el;
  6381. me.callParent();
  6382. if (!src) {
  6383. // We do this trick to allow the Sass to calculate resource image path for
  6384. // our package and pick up the proper image URL here.
  6385. src = el.getStyle('background-image');
  6386. src = src.substring(4, src.length - 1);
  6387. // strip off outer "url(...)"
  6388. // In IE8 this path will have quotes around it
  6389. if (src.indexOf('"') === 0) {
  6390. src = src.substring(1, src.length - 1);
  6391. }
  6392. // Then remember it on our prototype for any subsequent instances.
  6393. Ext.ux.colorpick.SliderHue.prototype.gradientUrl = src;
  6394. }
  6395. // Now clear that style because it will conflict with the background-color
  6396. el.setStyle('background-image', 'none');
  6397. // Create the image with the background PNG
  6398. el = me.getDragContainer().el;
  6399. el.createChild({
  6400. tag: 'img',
  6401. cls: Ext.baseCSSPrefix + 'colorpicker-hue-gradient',
  6402. src: src
  6403. });
  6404. },
  6405. // Called via data binding whenever selectedColor.h changes; hue param is 0-1
  6406. setHue: function(hue) {
  6407. var me = this,
  6408. container = me.getDragContainer(),
  6409. dragHandle = me.getDragHandle(),
  6410. containerEl = container.bodyElement,
  6411. containerHeight = containerEl.getHeight(),
  6412. top, yRatio;
  6413. hue = hue > 1 ? hue / 360 : hue;
  6414. // User actively dragging? Skip event
  6415. if (dragHandle.isDragging) {
  6416. return;
  6417. }
  6418. // y-axis of slider with value 0-1 translates to reverse of "saturation"
  6419. yRatio = 1 - hue;
  6420. top = containerHeight * yRatio;
  6421. // Position dragger
  6422. dragHandle.element.setStyle({
  6423. top: top + 'px'
  6424. });
  6425. }
  6426. });
  6427. /**
  6428. * Sencha Pro Services presents xtype "colorselector".
  6429. * API has been kept as close to the regular colorpicker as possible. The Selector can be
  6430. * rendered to any container.
  6431. *
  6432. * The defaul selected color is configurable via {@link #value} config
  6433. * and The Format is configurable via {@link #format}. Usually used in
  6434. * forms via {@link Ext.ux.colorpick.Button} or {@link Ext.ux.colorpick.Field}.
  6435. *
  6436. * Typically you will need to listen for the change event to be notified when the user
  6437. * chooses a color. Alternatively, you can bind to the "value" config
  6438. *
  6439. * @example
  6440. * Ext.create('Ext.ux.colorpick.Selector', {
  6441. * value : '993300', // initial selected color
  6442. * format : 'hex6', // by default it's hex6
  6443. * renderTo : Ext.getBody(),
  6444. *
  6445. * listeners: {
  6446. * change: function (colorselector, color) {
  6447. * console.log('New color: ' + color);
  6448. * }
  6449. * }
  6450. * });
  6451. */
  6452. Ext.define('Ext.ux.colorpick.Selector', {
  6453. extend: 'Ext.panel.Panel',
  6454. xtype: 'colorselector',
  6455. mixins: [
  6456. 'Ext.ux.colorpick.Selection'
  6457. ],
  6458. controller: 'colorpick-selectorcontroller',
  6459. requires: [
  6460. 'Ext.field.Text',
  6461. 'Ext.field.Number',
  6462. 'Ext.ux.colorpick.ColorMap',
  6463. 'Ext.ux.colorpick.SelectorModel',
  6464. 'Ext.ux.colorpick.SelectorController',
  6465. 'Ext.ux.colorpick.ColorPreview',
  6466. 'Ext.ux.colorpick.Slider',
  6467. 'Ext.ux.colorpick.SliderAlpha',
  6468. 'Ext.ux.colorpick.SliderSaturation',
  6469. 'Ext.ux.colorpick.SliderValue',
  6470. 'Ext.ux.colorpick.SliderHue'
  6471. ],
  6472. config: {
  6473. hexReadOnly: false
  6474. },
  6475. /**
  6476. * default width and height gives 255x255 color map in Crisp
  6477. */
  6478. width: Ext.platformTags.phone ? 'auto' : 580,
  6479. height: 337,
  6480. cls: Ext.baseCSSPrefix + 'colorpicker',
  6481. padding: 10,
  6482. layout: {
  6483. type: Ext.platformTags.phone ? 'vbox' : 'hbox',
  6484. align: 'stretch'
  6485. },
  6486. defaultBindProperty: 'value',
  6487. twoWayBindable: [
  6488. 'value',
  6489. 'hidden'
  6490. ],
  6491. /**
  6492. * @cfg fieldWidth {Number} Width of the text fields on the container (excluding HEX);
  6493. * since the width of the slider containers is the same as the text field under it
  6494. * (it's the same vbox column), changing this value will also affect the spacing between
  6495. * the sliders.
  6496. */
  6497. fieldWidth: 50,
  6498. /**
  6499. * @cfg fieldPad {Number} padding between the sliders and HEX/R/G/B fields.
  6500. */
  6501. fieldPad: 5,
  6502. /**
  6503. * @cfg {Boolean} [showPreviousColor]
  6504. * Whether "previous color" region (in upper right, below the selected color preview) should
  6505. * be shown;
  6506. * these are relied upon by the {@link Ext.ux.colorpick.Button} and the
  6507. * {@link Ext.ux.colorpick.Field}.
  6508. */
  6509. showPreviousColor: false,
  6510. /**
  6511. * @cfg {String} [okButtonText]
  6512. * Text value for "Ok" button;
  6513. * these are relied upon by the {@link Ext.ux.colorpick.Button} and the
  6514. * {@link Ext.ux.colorpick.Field}.
  6515. */
  6516. okButtonText: 'OK',
  6517. /**
  6518. * @cfg {String} [cancelButtonText]
  6519. * Text value for "Cancel" button;
  6520. * these are relied upon by the {@link Ext.ux.colorpick.Button} and the
  6521. * {@link Ext.ux.colorpick.Field}.
  6522. */
  6523. cancelButtonText: 'Cancel',
  6524. /**
  6525. * @cfg {Boolean} [showOkCancelButtons]
  6526. * Whether Ok and Cancel buttons (in upper right, below the selected color preview) should
  6527. * be shown;
  6528. * these are relied upon by the {@link Ext.ux.colorpick.Button} and the
  6529. * {@link Ext.ux.colorpick.Field}.
  6530. */
  6531. showOkCancelButtons: false,
  6532. /**
  6533. * @event change
  6534. * Fires when a color is selected. Simply dragging sliders around will trigger this.
  6535. * @param {Ext.ux.colorpick.Selector} this
  6536. * @param {String} color The value of the selected color as per specified {@link #format}.
  6537. * @param {String} previousColor The previous color value.
  6538. */
  6539. /**
  6540. * @event ok
  6541. * Fires when OK button is clicked (see {@link #showOkCancelButtons}).
  6542. * @param {Ext.ux.colorpick.Selector} this
  6543. * @param {String} color The value of the selected color as per specified {@link #format}.
  6544. */
  6545. /**
  6546. * @event cancel
  6547. * Fires when Cancel button is clicked (see {@link #showOkCancelButtons}).
  6548. * @param {Ext.ux.colorpick.Selector} this
  6549. */
  6550. listeners: {
  6551. resize: 'onResize',
  6552. show: 'onResize'
  6553. },
  6554. initConfig: function(config) {
  6555. var me = this,
  6556. childViewModel = Ext.Factory.viewModel('colorpick-selectormodel');
  6557. // Since this component needs to present its value as a thing to which users can
  6558. // bind, we create an internal VM for our purposes.
  6559. me.childViewModel = childViewModel;
  6560. if (Ext.platformTags.phone && !(Ext.Viewport.getOrientation() === "landscape")) {
  6561. me.fieldWidth = 35;
  6562. }
  6563. if (Ext.platformTags.phone) {
  6564. config.items = [
  6565. me.getPreviewForMobile(childViewModel, config),
  6566. {
  6567. xtype: 'container',
  6568. padding: '4px 0 0 0',
  6569. layout: {
  6570. type: 'hbox',
  6571. align: 'stretch'
  6572. },
  6573. flex: 1,
  6574. items: [
  6575. me.getMapAndHexRGBFields(childViewModel),
  6576. me.getSliderAndHField(childViewModel),
  6577. me.getSliderAndSField(childViewModel),
  6578. me.getSliderAndVField(childViewModel),
  6579. me.getSliderAndAField(childViewModel)
  6580. ]
  6581. },
  6582. me.getButtonForMobile(childViewModel, config)
  6583. ];
  6584. } else {
  6585. config.items = [
  6586. me.getMapAndHexRGBFields(childViewModel),
  6587. me.getSliderAndHField(childViewModel),
  6588. me.getSliderAndSField(childViewModel),
  6589. me.getSliderAndVField(childViewModel),
  6590. me.getSliderAndAField(childViewModel),
  6591. me.getPreviewAndButtons(childViewModel, config)
  6592. ];
  6593. }
  6594. me.childViewModel.bind('{selectedColor}', function(color) {
  6595. me.setColor(color);
  6596. });
  6597. this.callParent(arguments);
  6598. },
  6599. updateColor: function(color) {
  6600. var me = this;
  6601. me.mixins.colorselection.updateColor.call(me, color);
  6602. me.childViewModel.set('selectedColor', color);
  6603. },
  6604. updatePreviousColor: function(color) {
  6605. this.childViewModel.set('previousColor', color);
  6606. },
  6607. // Splits up view declaration for readability
  6608. // "Map" and HEX/R/G/B fields
  6609. getMapAndHexRGBFields: function(childViewModel) {
  6610. var me = this,
  6611. fieldMargin = '0 ' + me.fieldPad + ' 0 0',
  6612. fieldWidth = me.fieldWidth;
  6613. return {
  6614. xtype: 'container',
  6615. viewModel: childViewModel,
  6616. cls: Ext.baseCSSPrefix + 'colorpicker-escape-overflow',
  6617. flex: 1,
  6618. autoSize: false,
  6619. layout: {
  6620. type: 'vbox',
  6621. constrainAlign: true
  6622. },
  6623. margin: '0 10 0 0',
  6624. items: [
  6625. // "MAP"
  6626. {
  6627. xtype: 'colorpickercolormap',
  6628. reference: 'colorMap',
  6629. flex: 1,
  6630. bind: {
  6631. position: {
  6632. bindTo: '{selectedColor}',
  6633. deep: true
  6634. },
  6635. hue: '{selectedColor.h}'
  6636. },
  6637. listeners: {
  6638. handledrag: 'onColorMapHandleDrag'
  6639. }
  6640. },
  6641. // HEX/R/G/B FIELDS
  6642. {
  6643. xtype: 'container',
  6644. layout: 'hbox',
  6645. autoSize: null,
  6646. defaults: {
  6647. labelAlign: 'top',
  6648. allowBlank: false
  6649. },
  6650. items: [
  6651. {
  6652. xtype: 'textfield',
  6653. label: 'HEX',
  6654. flex: 1,
  6655. bind: '{hex}',
  6656. clearable: Ext.platformTags.phone ? false : true,
  6657. margin: fieldMargin,
  6658. validators: /^#[0-9a-f]{6}$/i,
  6659. readOnly: me.getHexReadOnly(),
  6660. required: true
  6661. },
  6662. {
  6663. xtype: 'numberfield',
  6664. clearable: false,
  6665. label: 'R',
  6666. bind: '{red}',
  6667. width: fieldWidth,
  6668. hideTrigger: true,
  6669. validators: /^(0|[1-9]\d*)$/i,
  6670. maxValue: 255,
  6671. minValue: 0,
  6672. margin: fieldMargin,
  6673. required: true
  6674. },
  6675. {
  6676. xtype: 'numberfield',
  6677. clearable: false,
  6678. label: 'G',
  6679. bind: '{green}',
  6680. width: fieldWidth,
  6681. hideTrigger: true,
  6682. validators: /^(0|[1-9]\d*)$/i,
  6683. maxValue: 255,
  6684. minValue: 0,
  6685. margin: fieldMargin,
  6686. required: true
  6687. },
  6688. {
  6689. xtype: 'numberfield',
  6690. clearable: false,
  6691. label: 'B',
  6692. bind: '{blue}',
  6693. width: fieldWidth,
  6694. hideTrigger: true,
  6695. validators: /^(0|[1-9]\d*)$/i,
  6696. maxValue: 255,
  6697. minValue: 0,
  6698. margin: 0,
  6699. required: true
  6700. }
  6701. ]
  6702. }
  6703. ]
  6704. };
  6705. },
  6706. // Splits up view declaration for readability
  6707. // Slider and H field
  6708. getSliderAndHField: function(childViewModel) {
  6709. var me = this,
  6710. fieldWidth = me.fieldWidth;
  6711. return {
  6712. xtype: 'container',
  6713. viewModel: childViewModel,
  6714. cls: Ext.baseCSSPrefix + 'colorpicker-escape-overflow',
  6715. width: fieldWidth,
  6716. layout: {
  6717. type: 'vbox',
  6718. align: 'stretch'
  6719. },
  6720. items: [
  6721. {
  6722. xtype: 'colorpickersliderhue',
  6723. reference: 'hueSlider',
  6724. flex: 1,
  6725. bind: {
  6726. hue: '{selectedColor.h}'
  6727. },
  6728. width: fieldWidth,
  6729. listeners: {
  6730. handledrag: 'onHueSliderHandleDrag'
  6731. }
  6732. },
  6733. {
  6734. xtype: 'numberfield',
  6735. reference: 'hnumberfield',
  6736. clearable: false,
  6737. label: 'H',
  6738. labelAlign: 'top',
  6739. bind: '{hue}',
  6740. hideTrigger: true,
  6741. maxValue: 360,
  6742. minValue: 0,
  6743. allowBlank: false,
  6744. margin: 0,
  6745. required: true
  6746. }
  6747. ]
  6748. };
  6749. },
  6750. // Splits up view declaration for readability
  6751. // Slider and S field
  6752. getSliderAndSField: function(childViewModel) {
  6753. var me = this,
  6754. fieldWidth = me.fieldWidth,
  6755. fieldPad = me.fieldPad;
  6756. return {
  6757. xtype: 'container',
  6758. viewModel: childViewModel,
  6759. cls: [
  6760. Ext.baseCSSPrefix + 'colorpicker-escape-overflow',
  6761. Ext.baseCSSPrefix + 'colorpicker-column-sslider'
  6762. ],
  6763. width: fieldWidth,
  6764. layout: {
  6765. type: 'vbox',
  6766. align: 'stretch'
  6767. },
  6768. margin: '0 ' + fieldPad + ' 0 ' + fieldPad,
  6769. items: [
  6770. {
  6771. xtype: 'colorpickerslidersaturation',
  6772. reference: 'satSlider',
  6773. flex: 1,
  6774. bind: {
  6775. saturation: '{saturation}',
  6776. hue: '{selectedColor.h}'
  6777. },
  6778. width: fieldWidth,
  6779. listeners: {
  6780. handledrag: 'onSaturationSliderHandleDrag'
  6781. }
  6782. },
  6783. {
  6784. xtype: 'numberfield',
  6785. reference: 'snumberfield',
  6786. clearable: false,
  6787. label: 'S',
  6788. labelAlign: 'top',
  6789. bind: '{saturation}',
  6790. hideTrigger: true,
  6791. maxValue: 100,
  6792. minValue: 0,
  6793. allowBlank: false,
  6794. margin: 0,
  6795. required: true
  6796. }
  6797. ]
  6798. };
  6799. },
  6800. // Splits up view declaration for readability
  6801. // Slider and V field
  6802. getSliderAndVField: function(childViewModel) {
  6803. var me = this,
  6804. fieldWidth = me.fieldWidth;
  6805. return {
  6806. xtype: 'container',
  6807. viewModel: childViewModel,
  6808. cls: [
  6809. Ext.baseCSSPrefix + 'colorpicker-escape-overflow',
  6810. Ext.baseCSSPrefix + 'colorpicker-column-vslider'
  6811. ],
  6812. width: fieldWidth,
  6813. layout: {
  6814. type: 'vbox',
  6815. align: 'stretch'
  6816. },
  6817. items: [
  6818. {
  6819. xtype: 'colorpickerslidervalue',
  6820. reference: 'valueSlider',
  6821. flex: 1,
  6822. bind: {
  6823. value: '{value}',
  6824. hue: '{selectedColor.h}'
  6825. },
  6826. width: fieldWidth,
  6827. listeners: {
  6828. handledrag: 'onValueSliderHandleDrag'
  6829. }
  6830. },
  6831. {
  6832. xtype: 'numberfield',
  6833. reference: 'vnumberfield',
  6834. clearable: false,
  6835. label: 'V',
  6836. labelAlign: 'top',
  6837. bind: '{value}',
  6838. hideTrigger: true,
  6839. maxValue: 100,
  6840. minValue: 0,
  6841. allowBlank: false,
  6842. margin: 0,
  6843. required: true
  6844. }
  6845. ]
  6846. };
  6847. },
  6848. // Splits up view declaration for readability
  6849. // Slider and A field
  6850. getSliderAndAField: function(childViewModel) {
  6851. var me = this,
  6852. fieldWidth = me.fieldWidth;
  6853. return {
  6854. xtype: 'container',
  6855. viewModel: childViewModel,
  6856. cls: Ext.baseCSSPrefix + 'colorpicker-escape-overflow',
  6857. width: fieldWidth,
  6858. layout: {
  6859. type: 'vbox',
  6860. align: 'stretch'
  6861. },
  6862. margin: '0 0 0 ' + me.fieldPad,
  6863. items: [
  6864. {
  6865. xtype: 'colorpickerslideralpha',
  6866. reference: 'alphaSlider',
  6867. flex: 1,
  6868. bind: {
  6869. alpha: '{alpha}',
  6870. color: {
  6871. bindTo: '{selectedColor}',
  6872. deep: true
  6873. }
  6874. },
  6875. width: fieldWidth,
  6876. listeners: {
  6877. handledrag: 'onAlphaSliderHandleDrag'
  6878. }
  6879. },
  6880. {
  6881. xtype: 'numberfield',
  6882. reference: 'anumberfield',
  6883. clearable: false,
  6884. label: 'A',
  6885. labelAlign: 'top',
  6886. bind: '{alpha}',
  6887. hideTrigger: true,
  6888. maxValue: 100,
  6889. minValue: 0,
  6890. allowBlank: false,
  6891. margin: 0,
  6892. required: true
  6893. }
  6894. ]
  6895. };
  6896. },
  6897. // Splits up view declaration for readability
  6898. // Preview current/previous color squares and OK and Cancel buttons
  6899. getPreviewAndButtons: function(childViewModel, config) {
  6900. // selected color preview is always shown
  6901. var items = [
  6902. {
  6903. xtype: 'colorpickercolorpreview',
  6904. flex: 1,
  6905. bind: {
  6906. color: {
  6907. bindTo: '{selectedColor}',
  6908. deep: true
  6909. }
  6910. }
  6911. }
  6912. ];
  6913. // previous color preview is optional
  6914. if (config.showPreviousColor) {
  6915. items.push({
  6916. xtype: 'colorpickercolorpreview',
  6917. flex: 1,
  6918. bind: {
  6919. color: {
  6920. bindTo: '{previousColor}',
  6921. deep: true
  6922. }
  6923. },
  6924. listeners: {
  6925. click: 'onPreviousColorSelected'
  6926. }
  6927. });
  6928. }
  6929. // Ok/Cancel buttons are optional
  6930. if (config.showOkCancelButtons) {
  6931. items.push({
  6932. xtype: 'button',
  6933. text: this.okButtonText,
  6934. margin: '10 0 0 0',
  6935. handler: 'onOK'
  6936. }, {
  6937. xtype: 'button',
  6938. text: this.cancelButtonText,
  6939. margin: '10 0 0 0',
  6940. handler: 'onCancel'
  6941. });
  6942. }
  6943. return {
  6944. xtype: 'container',
  6945. viewModel: childViewModel,
  6946. cls: Ext.baseCSSPrefix + 'colorpicker-column-preview',
  6947. width: 70,
  6948. margin: '0 0 0 10',
  6949. items: items,
  6950. layout: {
  6951. type: 'vbox',
  6952. align: 'stretch'
  6953. }
  6954. };
  6955. },
  6956. getPreviewForMobile: function(childViewModel, config) {
  6957. // selected color preview is always shown
  6958. var items = [
  6959. {
  6960. xtype: 'colorpickercolorpreview',
  6961. flex: 1,
  6962. bind: {
  6963. color: {
  6964. bindTo: '{selectedColor}',
  6965. deep: true
  6966. }
  6967. }
  6968. }
  6969. ];
  6970. // previous color preview is optional
  6971. if (config.showPreviousColor) {
  6972. items.push({
  6973. xtype: 'colorpickercolorpreview',
  6974. flex: 1,
  6975. bind: {
  6976. color: {
  6977. bindTo: '{previousColor}',
  6978. deep: true
  6979. }
  6980. },
  6981. listeners: {
  6982. click: 'onPreviousColorSelected'
  6983. }
  6984. });
  6985. }
  6986. return {
  6987. xtype: 'container',
  6988. viewModel: childViewModel,
  6989. cls: Ext.baseCSSPrefix + 'colorpicker-column-mobile-preview',
  6990. // width: '100%',
  6991. height: 40,
  6992. margin: '10 0 10 0',
  6993. items: items,
  6994. layout: {
  6995. type: 'hbox',
  6996. align: 'stretch'
  6997. }
  6998. };
  6999. },
  7000. getButtonForMobile: function(childViewModel, config) {
  7001. // selected color preview is always shown
  7002. var items = [];
  7003. // Ok/Cancel buttons are optional
  7004. if (config.showOkCancelButtons) {
  7005. items.push({
  7006. xtype: 'container',
  7007. flex: 1
  7008. }, {
  7009. xtype: 'button',
  7010. text: this.cancelButtonText,
  7011. minWidth: 70,
  7012. margin: '5 5 0 5',
  7013. handler: 'onCancel'
  7014. }, {
  7015. xtype: 'button',
  7016. text: this.okButtonText,
  7017. margin: '5 5 0 5',
  7018. minWidth: 50,
  7019. handler: 'onOK'
  7020. });
  7021. return {
  7022. xtype: 'container',
  7023. viewModel: childViewModel,
  7024. cls: Ext.baseCSSPrefix + 'colorpicker-column-mobile-button',
  7025. width: '100%',
  7026. height: 40,
  7027. margin: '0',
  7028. align: 'right',
  7029. items: items,
  7030. layout: {
  7031. type: 'hbox',
  7032. align: 'stretch'
  7033. }
  7034. };
  7035. }
  7036. return {};
  7037. }
  7038. });
  7039. /**
  7040. * @private
  7041. */
  7042. Ext.define('Ext.ux.colorpick.ButtonController', {
  7043. extend: 'Ext.app.ViewController',
  7044. alias: 'controller.colorpick-buttoncontroller',
  7045. requires: [
  7046. 'Ext.Dialog',
  7047. 'Ext.ux.colorpick.Selector',
  7048. 'Ext.ux.colorpick.ColorUtils'
  7049. ],
  7050. afterRender: function(view) {
  7051. view.updateColor(view.getColor());
  7052. },
  7053. destroy: function() {
  7054. var view = this.getView(),
  7055. colorPickerWindow = view.colorPickerWindow;
  7056. if (colorPickerWindow) {
  7057. colorPickerWindow.destroy();
  7058. view.colorPickerWindow = view.colorPicker = null;
  7059. }
  7060. this.callParent();
  7061. },
  7062. getPopup: function() {
  7063. var view = this.getView(),
  7064. popup = view.colorPickerWindow,
  7065. selector;
  7066. if (!popup) {
  7067. popup = Ext.create(view.getPopup());
  7068. view.colorPickerWindow = popup;
  7069. popup.colorPicker = view.colorPicker = selector = popup.lookupReference('selector');
  7070. selector.setFormat(view.getFormat());
  7071. selector.on({
  7072. ok: 'onColorPickerOK',
  7073. cancel: 'onColorPickerCancel',
  7074. scope: this
  7075. });
  7076. popup.on({
  7077. close: 'onColorPickerCancel',
  7078. scope: this
  7079. });
  7080. }
  7081. return popup;
  7082. },
  7083. // When button is clicked show the color picker window
  7084. onClick: function() {
  7085. var me = this,
  7086. view = me.getView(),
  7087. color = view.getColor(),
  7088. popup = me.getPopup(),
  7089. colorPicker = popup.colorPicker;
  7090. colorPicker.setColor(color);
  7091. colorPicker.setPreviousColor(color);
  7092. popup.show();
  7093. },
  7094. onColorPickerOK: function(picker) {
  7095. var view = this.getView(),
  7096. color = picker.getColor(),
  7097. cpWin = view.colorPickerWindow;
  7098. cpWin.hide();
  7099. view.setColor(color);
  7100. },
  7101. onColorPickerCancel: function() {
  7102. var view = this.getView(),
  7103. cpWin = view.colorPickerWindow;
  7104. cpWin.hide();
  7105. },
  7106. syncColor: function(color) {
  7107. var view = this.getView();
  7108. Ext.ux.colorpick.ColorUtils.setBackground(view.filterEl, color);
  7109. }
  7110. });
  7111. /**
  7112. * A simple color swatch that can be clicked to bring up the color selector.
  7113. *
  7114. * The selected color is configurable via {@link #value} and
  7115. * The Format is configurable via {@link #format}.
  7116. *
  7117. * @example
  7118. * Ext.create('Ext.ux.colorpick.Button', {
  7119. * value: '993300', // initial selected color
  7120. * format: 'hex6', // by default it's hex6
  7121. * renderTo: Ext.getBody(),
  7122. *
  7123. * listeners: {
  7124. * select: function(picker, selColor) {
  7125. * Ext.Msg.alert('Color', selColor);
  7126. * }
  7127. * }
  7128. * });
  7129. */
  7130. Ext.define('Ext.ux.colorpick.Button', {
  7131. extend: 'Ext.Component',
  7132. xtype: 'colorbutton',
  7133. controller: 'colorpick-buttoncontroller',
  7134. mixins: [
  7135. 'Ext.ux.colorpick.Selection'
  7136. ],
  7137. requires: [
  7138. 'Ext.ux.colorpick.ButtonController'
  7139. ],
  7140. baseCls: Ext.baseCSSPrefix + 'colorpicker-button',
  7141. width: 20,
  7142. height: 20,
  7143. childEls: [
  7144. 'btnEl',
  7145. 'filterEl'
  7146. ],
  7147. config: {
  7148. /**
  7149. * @cfg {Object} popup
  7150. * This object configures the popup window and colorselector component displayed
  7151. * when this button is clicked. Applications should not need to configure this.
  7152. * @private
  7153. */
  7154. popup: {
  7155. lazy: true,
  7156. $value: {
  7157. xtype: 'dialog',
  7158. closeAction: 'hide',
  7159. referenceHolder: true,
  7160. header: false,
  7161. resizable: true,
  7162. scrollable: true,
  7163. items: {
  7164. xtype: 'colorselector',
  7165. reference: 'selector',
  7166. flex: '1 1 auto',
  7167. showPreviousColor: true,
  7168. showOkCancelButtons: true
  7169. }
  7170. }
  7171. }
  7172. },
  7173. defaultBindProperty: 'value',
  7174. twoWayBindable: 'value',
  7175. getTemplate: function() {
  7176. return [
  7177. {
  7178. reference: 'filterEl',
  7179. cls: Ext.baseCSSPrefix + 'colorbutton-filter-el'
  7180. },
  7181. {
  7182. reference: 'btnEl',
  7183. tag: 'a',
  7184. cls: Ext.baseCSSPrefix + 'colorbutton-btn-el'
  7185. }
  7186. ];
  7187. },
  7188. listeners: {
  7189. click: 'onClick',
  7190. element: 'btnEl'
  7191. },
  7192. /**
  7193. * @event change
  7194. * Fires when a color is selected.
  7195. * @param {Ext.ux.colorpick.Selector} this
  7196. * @param {String} color The value of the selected color as per specified {@link #format}.
  7197. * @param {String} previousColor The previous color value.
  7198. */
  7199. updateColor: function(color) {
  7200. var me = this,
  7201. cp = me.colorPicker;
  7202. me.mixins.colorselection.updateColor.call(me, color);
  7203. Ext.ux.colorpick.ColorUtils.setBackground(me.filterEl, color);
  7204. if (cp) {
  7205. cp.setColor(color);
  7206. }
  7207. },
  7208. // Sets this.format and color picker's setFormat()
  7209. updateFormat: function(format) {
  7210. var cp = this.colorPicker;
  7211. if (cp) {
  7212. cp.setFormat(format);
  7213. }
  7214. }
  7215. });
  7216. /**
  7217. * A field that can be clicked to bring up the color picker.
  7218. * The selected color is configurable via {@link #value} and
  7219. * The Format is configurable via {@link #format}.
  7220. *
  7221. * @example
  7222. * Ext.create({
  7223. * xtype: 'colorfield',
  7224. * renderTo: Ext.getBody(),
  7225. *
  7226. * value: '#993300', // initial selected color
  7227. * format: 'hex6', // by default it's hex6
  7228. *
  7229. * listeners : {
  7230. * change: function (field, color) {
  7231. * console.log('New color: ' + color);
  7232. * }
  7233. * }
  7234. * });
  7235. */
  7236. Ext.define('Ext.ux.colorpick.Field', {
  7237. extend: 'Ext.field.Picker',
  7238. xtype: 'colorfield',
  7239. mixins: [
  7240. 'Ext.ux.colorpick.Selection'
  7241. ],
  7242. requires: [
  7243. 'Ext.window.Window',
  7244. 'Ext.ux.colorpick.Selector',
  7245. 'Ext.ux.colorpick.ColorUtils'
  7246. ],
  7247. editable: false,
  7248. focusable: true,
  7249. matchFieldWidth: false,
  7250. // picker is usually wider than field
  7251. // "Color Swatch" shown on the left of the field
  7252. html: [
  7253. '<div class="' + Ext.baseCSSPrefix + 'colorpicker-field-swatch">' + '<div class="' + Ext.baseCSSPrefix + 'colorpicker-field-swatch-inner"></div>' + '</div>'
  7254. ],
  7255. cls: Ext.baseCSSPrefix + 'colorpicker-field',
  7256. config: {
  7257. /**
  7258. * @cfg {Object} popup
  7259. * This object configures the popup window and colorselector component displayed
  7260. * when this button is clicked. Applications should not need to configure this.
  7261. * @private
  7262. */
  7263. popup: {
  7264. lazy: true,
  7265. $value: {
  7266. xtype: 'window',
  7267. closeAction: 'hide',
  7268. modal: Ext.platformTags.phone ? true : false,
  7269. referenceHolder: true,
  7270. width: Ext.platformTags.phone ? '100%' : 'auto',
  7271. layout: Ext.platformTags.phone ? 'hbox' : 'vbox',
  7272. header: false,
  7273. resizable: true,
  7274. scrollable: true,
  7275. items: {
  7276. xtype: 'colorselector',
  7277. reference: 'selector',
  7278. flex: '1 1 auto',
  7279. showPreviousColor: true,
  7280. showOkCancelButtons: true
  7281. }
  7282. }
  7283. }
  7284. },
  7285. /**
  7286. * @event change
  7287. * Fires when a color is selected.
  7288. * @param {Ext.ux.colorpick.Field} this
  7289. * @param {String} color The value of the selected color as per specified {@link #format}.
  7290. * @param {String} previousColor The previous color value.
  7291. */
  7292. afterRender: function() {
  7293. this.callParent();
  7294. this.updateValue(this.value);
  7295. },
  7296. // override as required by parent pickerfield
  7297. createFloatedPicker: function() {
  7298. var me = this,
  7299. popup = me.getPopup(),
  7300. picker;
  7301. // the window will actually be shown and will house the picker
  7302. me.colorPickerWindow = popup = Ext.create(popup);
  7303. picker = me.colorPicker = popup.lookupReference('selector');
  7304. picker.setColor(me.getColor());
  7305. picker.setHexReadOnly(!me.editable);
  7306. picker.on({
  7307. ok: 'onColorPickerOK',
  7308. cancel: 'onColorPickerCancel',
  7309. close: 'onColorPickerCancel',
  7310. scope: me
  7311. });
  7312. me.colorPicker.ownerCmp = me;
  7313. return me.colorPickerWindow;
  7314. },
  7315. // override as required by parent pickerfield for mobile devices
  7316. createEdgePicker: function() {
  7317. var me = this,
  7318. popup = me.getPopup(),
  7319. picker;
  7320. // the window will actually be shown and will house the picker
  7321. me.colorPickerWindow = popup = Ext.create(popup);
  7322. picker = me.colorPicker = popup.lookupReference('selector');
  7323. me.pickerType = 'floated';
  7324. picker.setColor(me.getColor());
  7325. picker.on({
  7326. ok: 'onColorPickerOK',
  7327. cancel: 'onColorPickerCancel',
  7328. close: 'onColorPickerCancel',
  7329. scope: me
  7330. });
  7331. me.colorPicker.ownerCmp = me;
  7332. return me.colorPickerWindow;
  7333. },
  7334. collapse: function() {
  7335. var picker = this.getPicker();
  7336. if (this.expanded) {
  7337. picker.hide();
  7338. }
  7339. },
  7340. showPicker: function() {
  7341. var me = this,
  7342. alignTarget = me[me.alignTarget],
  7343. picker = me.getPicker(),
  7344. color = this.getColor();
  7345. // Setting up previous selected color
  7346. if (this.colorPicker) {
  7347. this.colorPicker.setColor(this.getColor());
  7348. this.colorPicker.setPreviousColor(color);
  7349. }
  7350. // TODO: what if virtual keyboard is present
  7351. if (me.getMatchFieldWidth()) {
  7352. picker.setWidth(alignTarget.getWidth());
  7353. }
  7354. if (Ext.platformTags.phone) {
  7355. picker.show();
  7356. } else {
  7357. picker.showBy(alignTarget, me.getFloatedPickerAlign(), {
  7358. minHeight: 100
  7359. });
  7360. }
  7361. // Collapse on touch outside this component tree.
  7362. // Because touch platforms do not focus document.body on touch
  7363. // so no focusleave would occur to trigger a collapse.
  7364. me.touchListeners = Ext.getDoc().on({
  7365. // Do not translate on non-touch platforms.
  7366. // mousedown will blur the field.
  7367. translate: false,
  7368. touchstart: me.collapseIf,
  7369. scope: me,
  7370. delegated: false,
  7371. destroyable: true
  7372. });
  7373. },
  7374. onFocusLeave: function(e) {
  7375. if (e.type !== 'focusenter') {
  7376. this.callParent(arguments);
  7377. }
  7378. },
  7379. // When the Ok button is clicked on color picker, preserve the previous value
  7380. onColorPickerOK: function(colorPicker) {
  7381. this.setColor(colorPicker.getColor());
  7382. this.collapse();
  7383. },
  7384. onColorPickerCancel: function() {
  7385. this.collapse();
  7386. },
  7387. onExpandTap: function() {
  7388. var color = this.getColor();
  7389. if (this.colorPicker) {
  7390. this.colorPicker.setPreviousColor(color);
  7391. }
  7392. this.callParent(arguments);
  7393. },
  7394. // Expects value formatted as per "format" config
  7395. setValue: function(color) {
  7396. var me = this,
  7397. c;
  7398. if (Ext.ux.colorpick.ColorUtils.isValid(color)) {
  7399. c = me.mixins.colorselection.applyValue.call(me, color);
  7400. me.callParent([
  7401. c
  7402. ]);
  7403. }
  7404. },
  7405. // Sets this.format and color picker's setFormat()
  7406. updateFormat: function(format) {
  7407. var cp = this.colorPicker;
  7408. if (cp) {
  7409. cp.setFormat(format);
  7410. }
  7411. },
  7412. updateValue: function(color) {
  7413. var me = this,
  7414. swatchEl = this.element.down('.x-colorpicker-field-swatch-inner'),
  7415. c;
  7416. // If the "value" is changed, update "color" as well. Since these are always
  7417. // tracking each other, we guard against the case where we are being updated
  7418. // *because* "color" is being set.
  7419. if (!me.syncing) {
  7420. me.syncing = true;
  7421. me.setColor(color);
  7422. me.syncing = false;
  7423. }
  7424. c = me.getColor();
  7425. Ext.ux.colorpick.ColorUtils.setBackground(swatchEl, c);
  7426. if (me.colorPicker) {
  7427. me.colorPicker.setColor(c);
  7428. }
  7429. me.inputElement.dom.value = me.getValue();
  7430. },
  7431. validator: function(val) {
  7432. if (!Ext.ux.colorpick.ColorUtils.isValid(val)) {
  7433. return this.invalidText;
  7434. }
  7435. return true;
  7436. },
  7437. updateColor: function(color) {
  7438. var me = this,
  7439. cp = me.colorPicker,
  7440. swatchEl = this.element.down('.x-colorpicker-field-swatch-inner');
  7441. me.mixins.colorselection.updateColor.call(me, color);
  7442. Ext.ux.colorpick.ColorUtils.setBackground(swatchEl, color);
  7443. if (cp) {
  7444. cp.setColor(color);
  7445. }
  7446. }
  7447. });