ux-debug.js 498 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025902690279028902990309031903290339034903590369037903890399040904190429043904490459046904790489049905090519052905390549055905690579058905990609061906290639064906590669067906890699070907190729073907490759076907790789079908090819082908390849085908690879088908990909091909290939094909590969097909890999100910191029103910491059106910791089109911091119112911391149115911691179118911991209121912291239124912591269127912891299130913191329133913491359136913791389139914091419142914391449145914691479148914991509151915291539154915591569157915891599160916191629163916491659166916791689169917091719172917391749175917691779178917991809181918291839184918591869187918891899190919191929193919491959196919791989199920092019202920392049205920692079208920992109211921292139214921592169217921892199220922192229223922492259226922792289229923092319232923392349235923692379238923992409241924292439244924592469247924892499250925192529253925492559256925792589259926092619262926392649265926692679268926992709271927292739274927592769277927892799280928192829283928492859286928792889289929092919292929392949295929692979298929993009301930293039304930593069307930893099310931193129313931493159316931793189319932093219322932393249325932693279328932993309331933293339334933593369337933893399340934193429343934493459346934793489349935093519352935393549355935693579358935993609361936293639364936593669367936893699370937193729373937493759376937793789379938093819382938393849385938693879388938993909391939293939394939593969397939893999400940194029403940494059406940794089409941094119412941394149415941694179418941994209421942294239424942594269427942894299430943194329433943494359436943794389439944094419442944394449445944694479448944994509451945294539454945594569457945894599460946194629463946494659466946794689469947094719472947394749475947694779478947994809481948294839484948594869487948894899490949194929493949494959496949794989499950095019502950395049505950695079508950995109511951295139514951595169517951895199520952195229523952495259526952795289529953095319532953395349535953695379538953995409541954295439544954595469547954895499550955195529553955495559556955795589559956095619562956395649565956695679568956995709571957295739574957595769577957895799580958195829583958495859586958795889589959095919592959395949595959695979598959996009601960296039604960596069607960896099610961196129613961496159616961796189619962096219622962396249625962696279628962996309631963296339634963596369637963896399640964196429643964496459646964796489649965096519652965396549655965696579658965996609661966296639664966596669667966896699670967196729673967496759676967796789679968096819682968396849685968696879688968996909691969296939694969596969697969896999700970197029703970497059706970797089709971097119712971397149715971697179718971997209721972297239724972597269727972897299730973197329733973497359736973797389739974097419742974397449745974697479748974997509751975297539754975597569757975897599760976197629763976497659766976797689769977097719772977397749775977697779778977997809781978297839784978597869787978897899790979197929793979497959796979797989799980098019802980398049805980698079808980998109811981298139814981598169817981898199820982198229823982498259826982798289829983098319832983398349835983698379838983998409841984298439844984598469847984898499850985198529853985498559856985798589859986098619862986398649865986698679868986998709871987298739874987598769877987898799880988198829883988498859886988798889889989098919892989398949895989698979898989999009901990299039904990599069907990899099910991199129913991499159916991799189919992099219922992399249925992699279928992999309931993299339934993599369937993899399940994199429943994499459946994799489949995099519952995399549955995699579958995999609961996299639964996599669967996899699970997199729973997499759976997799789979998099819982998399849985998699879988998999909991999299939994999599969997999899991000010001100021000310004100051000610007100081000910010100111001210013100141001510016100171001810019100201002110022100231002410025100261002710028100291003010031100321003310034100351003610037100381003910040100411004210043100441004510046100471004810049100501005110052100531005410055100561005710058100591006010061100621006310064100651006610067100681006910070100711007210073100741007510076100771007810079100801008110082100831008410085100861008710088100891009010091100921009310094100951009610097100981009910100101011010210103101041010510106101071010810109101101011110112101131011410115101161011710118101191012010121101221012310124101251012610127101281012910130101311013210133101341013510136101371013810139101401014110142101431014410145101461014710148101491015010151101521015310154101551015610157101581015910160101611016210163101641016510166101671016810169101701017110172101731017410175101761017710178101791018010181101821018310184101851018610187101881018910190101911019210193101941019510196101971019810199102001020110202102031020410205102061020710208102091021010211102121021310214102151021610217102181021910220102211022210223102241022510226102271022810229102301023110232102331023410235102361023710238102391024010241102421024310244102451024610247102481024910250102511025210253102541025510256102571025810259102601026110262102631026410265102661026710268102691027010271102721027310274102751027610277102781027910280102811028210283102841028510286102871028810289102901029110292102931029410295102961029710298102991030010301103021030310304103051030610307103081030910310103111031210313103141031510316103171031810319103201032110322103231032410325103261032710328103291033010331103321033310334103351033610337103381033910340103411034210343103441034510346103471034810349103501035110352103531035410355103561035710358103591036010361103621036310364103651036610367103681036910370103711037210373103741037510376103771037810379103801038110382103831038410385103861038710388103891039010391103921039310394103951039610397103981039910400104011040210403104041040510406104071040810409104101041110412104131041410415104161041710418104191042010421104221042310424104251042610427104281042910430104311043210433104341043510436104371043810439104401044110442104431044410445104461044710448104491045010451104521045310454104551045610457104581045910460104611046210463104641046510466104671046810469104701047110472104731047410475104761047710478104791048010481104821048310484104851048610487104881048910490104911049210493104941049510496104971049810499105001050110502105031050410505105061050710508105091051010511105121051310514105151051610517105181051910520105211052210523105241052510526105271052810529105301053110532105331053410535105361053710538105391054010541105421054310544105451054610547105481054910550105511055210553105541055510556105571055810559105601056110562105631056410565105661056710568105691057010571105721057310574105751057610577105781057910580105811058210583105841058510586105871058810589105901059110592105931059410595105961059710598105991060010601106021060310604106051060610607106081060910610106111061210613106141061510616106171061810619106201062110622106231062410625106261062710628106291063010631106321063310634106351063610637106381063910640106411064210643106441064510646106471064810649106501065110652106531065410655106561065710658106591066010661106621066310664106651066610667106681066910670106711067210673106741067510676106771067810679106801068110682106831068410685106861068710688106891069010691106921069310694106951069610697106981069910700107011070210703107041070510706107071070810709107101071110712107131071410715107161071710718107191072010721107221072310724107251072610727107281072910730107311073210733107341073510736107371073810739107401074110742107431074410745107461074710748107491075010751107521075310754107551075610757107581075910760107611076210763107641076510766107671076810769107701077110772107731077410775107761077710778107791078010781107821078310784107851078610787107881078910790107911079210793107941079510796107971079810799108001080110802108031080410805108061080710808108091081010811108121081310814108151081610817108181081910820108211082210823108241082510826108271082810829108301083110832108331083410835108361083710838108391084010841108421084310844108451084610847108481084910850108511085210853108541085510856108571085810859108601086110862108631086410865108661086710868108691087010871108721087310874108751087610877108781087910880108811088210883108841088510886108871088810889108901089110892108931089410895108961089710898108991090010901109021090310904109051090610907109081090910910109111091210913109141091510916109171091810919109201092110922109231092410925109261092710928109291093010931109321093310934109351093610937109381093910940109411094210943109441094510946109471094810949109501095110952109531095410955109561095710958109591096010961109621096310964109651096610967109681096910970109711097210973109741097510976109771097810979109801098110982109831098410985109861098710988109891099010991109921099310994109951099610997109981099911000110011100211003110041100511006110071100811009110101101111012110131101411015110161101711018110191102011021110221102311024110251102611027110281102911030110311103211033110341103511036110371103811039110401104111042110431104411045110461104711048110491105011051110521105311054110551105611057110581105911060110611106211063110641106511066110671106811069110701107111072110731107411075110761107711078110791108011081110821108311084110851108611087110881108911090110911109211093110941109511096110971109811099111001110111102111031110411105111061110711108111091111011111111121111311114111151111611117111181111911120111211112211123111241112511126111271112811129111301113111132111331113411135111361113711138111391114011141111421114311144111451114611147111481114911150111511115211153111541115511156111571115811159111601116111162111631116411165111661116711168111691117011171111721117311174111751117611177111781117911180111811118211183111841118511186111871118811189111901119111192111931119411195111961119711198111991120011201112021120311204112051120611207112081120911210112111121211213112141121511216112171121811219112201122111222112231122411225112261122711228112291123011231112321123311234112351123611237112381123911240112411124211243112441124511246112471124811249112501125111252112531125411255112561125711258112591126011261112621126311264112651126611267112681126911270112711127211273112741127511276112771127811279112801128111282112831128411285112861128711288112891129011291112921129311294112951129611297112981129911300113011130211303113041130511306113071130811309113101131111312113131131411315113161131711318113191132011321113221132311324113251132611327113281132911330113311133211333113341133511336113371133811339113401134111342113431134411345113461134711348113491135011351113521135311354113551135611357113581135911360113611136211363113641136511366113671136811369113701137111372113731137411375113761137711378113791138011381113821138311384113851138611387113881138911390113911139211393113941139511396113971139811399114001140111402114031140411405114061140711408114091141011411114121141311414114151141611417114181141911420114211142211423114241142511426114271142811429114301143111432114331143411435114361143711438114391144011441114421144311444114451144611447114481144911450114511145211453114541145511456114571145811459114601146111462114631146411465114661146711468114691147011471114721147311474114751147611477114781147911480114811148211483114841148511486114871148811489114901149111492114931149411495114961149711498114991150011501115021150311504115051150611507115081150911510115111151211513115141151511516115171151811519115201152111522115231152411525115261152711528115291153011531115321153311534115351153611537115381153911540115411154211543115441154511546115471154811549115501155111552115531155411555115561155711558115591156011561115621156311564115651156611567115681156911570115711157211573115741157511576115771157811579115801158111582115831158411585115861158711588115891159011591115921159311594115951159611597115981159911600116011160211603116041160511606116071160811609116101161111612116131161411615116161161711618116191162011621116221162311624116251162611627116281162911630116311163211633116341163511636116371163811639116401164111642116431164411645116461164711648116491165011651116521165311654116551165611657116581165911660116611166211663116641166511666116671166811669116701167111672116731167411675116761167711678116791168011681116821168311684116851168611687116881168911690116911169211693116941169511696116971169811699117001170111702117031170411705117061170711708117091171011711117121171311714117151171611717117181171911720117211172211723117241172511726117271172811729117301173111732117331173411735117361173711738117391174011741117421174311744117451174611747117481174911750117511175211753117541175511756117571175811759117601176111762117631176411765117661176711768117691177011771117721177311774117751177611777117781177911780117811178211783117841178511786117871178811789117901179111792117931179411795117961179711798117991180011801118021180311804118051180611807118081180911810118111181211813118141181511816118171181811819118201182111822118231182411825118261182711828118291183011831118321183311834118351183611837118381183911840118411184211843118441184511846118471184811849118501185111852118531185411855118561185711858118591186011861118621186311864118651186611867118681186911870118711187211873118741187511876118771187811879118801188111882118831188411885118861188711888118891189011891118921189311894118951189611897118981189911900119011190211903119041190511906119071190811909119101191111912119131191411915119161191711918119191192011921119221192311924119251192611927119281192911930119311193211933119341193511936119371193811939119401194111942119431194411945119461194711948119491195011951119521195311954119551195611957119581195911960119611196211963119641196511966119671196811969119701197111972119731197411975119761197711978119791198011981119821198311984119851198611987119881198911990119911199211993119941199511996119971199811999120001200112002120031200412005120061200712008120091201012011120121201312014120151201612017120181201912020120211202212023120241202512026120271202812029120301203112032120331203412035120361203712038120391204012041120421204312044120451204612047120481204912050120511205212053120541205512056120571205812059120601206112062120631206412065120661206712068120691207012071120721207312074120751207612077120781207912080120811208212083120841208512086120871208812089120901209112092120931209412095120961209712098120991210012101121021210312104121051210612107121081210912110121111211212113121141211512116121171211812119121201212112122121231212412125121261212712128121291213012131121321213312134121351213612137121381213912140121411214212143121441214512146121471214812149121501215112152121531215412155121561215712158121591216012161121621216312164121651216612167121681216912170121711217212173121741217512176121771217812179121801218112182121831218412185121861218712188121891219012191121921219312194121951219612197121981219912200122011220212203122041220512206122071220812209122101221112212122131221412215122161221712218122191222012221122221222312224122251222612227122281222912230122311223212233122341223512236122371223812239122401224112242122431224412245122461224712248122491225012251122521225312254122551225612257122581225912260122611226212263122641226512266122671226812269122701227112272122731227412275122761227712278122791228012281122821228312284122851228612287122881228912290122911229212293122941229512296122971229812299123001230112302123031230412305123061230712308123091231012311123121231312314123151231612317123181231912320123211232212323123241232512326123271232812329123301233112332123331233412335123361233712338123391234012341123421234312344123451234612347123481234912350123511235212353123541235512356123571235812359123601236112362123631236412365123661236712368123691237012371123721237312374123751237612377123781237912380123811238212383123841238512386123871238812389123901239112392123931239412395123961239712398123991240012401124021240312404124051240612407124081240912410124111241212413124141241512416124171241812419124201242112422124231242412425124261242712428124291243012431124321243312434124351243612437124381243912440124411244212443124441244512446124471244812449124501245112452124531245412455124561245712458124591246012461124621246312464124651246612467124681246912470124711247212473124741247512476124771247812479124801248112482124831248412485124861248712488124891249012491124921249312494124951249612497124981249912500125011250212503125041250512506125071250812509125101251112512125131251412515125161251712518125191252012521125221252312524125251252612527125281252912530125311253212533125341253512536125371253812539125401254112542125431254412545125461254712548125491255012551125521255312554125551255612557125581255912560125611256212563125641256512566125671256812569125701257112572125731257412575125761257712578125791258012581125821258312584125851258612587125881258912590125911259212593125941259512596125971259812599126001260112602126031260412605126061260712608126091261012611126121261312614126151261612617126181261912620126211262212623126241262512626126271262812629126301263112632126331263412635126361263712638126391264012641126421264312644126451264612647126481264912650126511265212653126541265512656126571265812659126601266112662126631266412665126661266712668126691267012671126721267312674126751267612677126781267912680126811268212683126841268512686126871268812689126901269112692126931269412695126961269712698126991270012701127021270312704127051270612707127081270912710127111271212713127141271512716127171271812719127201272112722127231272412725127261272712728127291273012731127321273312734127351273612737127381273912740127411274212743127441274512746127471274812749127501275112752127531275412755127561275712758127591276012761127621276312764127651276612767127681276912770127711277212773127741277512776127771277812779127801278112782127831278412785127861278712788127891279012791127921279312794127951279612797127981279912800128011280212803128041280512806128071280812809128101281112812128131281412815128161281712818128191282012821128221282312824128251282612827128281282912830128311283212833128341283512836128371283812839128401284112842128431284412845128461284712848128491285012851128521285312854128551285612857128581285912860128611286212863128641286512866128671286812869128701287112872128731287412875128761287712878128791288012881128821288312884128851288612887128881288912890128911289212893128941289512896128971289812899129001290112902129031290412905129061290712908129091291012911129121291312914129151291612917129181291912920129211292212923129241292512926129271292812929129301293112932129331293412935129361293712938129391294012941129421294312944129451294612947129481294912950129511295212953129541295512956129571295812959129601296112962129631296412965129661296712968129691297012971129721297312974129751297612977129781297912980129811298212983129841298512986129871298812989129901299112992129931299412995129961299712998129991300013001130021300313004130051300613007130081300913010130111301213013130141301513016130171301813019130201302113022130231302413025130261302713028130291303013031130321303313034130351303613037130381303913040130411304213043130441304513046130471304813049130501305113052130531305413055130561305713058130591306013061130621306313064130651306613067130681306913070130711307213073130741307513076130771307813079130801308113082130831308413085130861308713088130891309013091130921309313094130951309613097130981309913100131011310213103131041310513106131071310813109131101311113112131131311413115131161311713118131191312013121131221312313124131251312613127131281312913130131311313213133131341313513136131371313813139131401314113142131431314413145131461314713148131491315013151131521315313154131551315613157131581315913160131611316213163131641316513166131671316813169131701317113172131731317413175131761317713178131791318013181131821318313184131851318613187131881318913190131911319213193131941319513196131971319813199132001320113202132031320413205132061320713208132091321013211132121321313214132151321613217132181321913220132211322213223132241322513226132271322813229132301323113232132331323413235132361323713238132391324013241132421324313244132451324613247132481324913250132511325213253132541325513256132571325813259132601326113262132631326413265132661326713268132691327013271132721327313274132751327613277132781327913280132811328213283132841328513286132871328813289132901329113292132931329413295132961329713298132991330013301133021330313304133051330613307133081330913310133111331213313133141331513316133171331813319133201332113322133231332413325133261332713328133291333013331133321333313334133351333613337133381333913340133411334213343133441334513346133471334813349133501335113352133531335413355133561335713358133591336013361133621336313364133651336613367133681336913370133711337213373133741337513376133771337813379133801338113382133831338413385133861338713388133891339013391133921339313394133951339613397133981339913400134011340213403134041340513406134071340813409134101341113412134131341413415134161341713418134191342013421134221342313424134251342613427134281342913430134311343213433134341343513436134371343813439134401344113442134431344413445134461344713448134491345013451134521345313454134551345613457134581345913460134611346213463134641346513466134671346813469134701347113472134731347413475134761347713478134791348013481134821348313484134851348613487134881348913490134911349213493134941349513496134971349813499135001350113502135031350413505135061350713508135091351013511135121351313514135151351613517135181351913520135211352213523135241352513526135271352813529135301353113532135331353413535135361353713538135391354013541135421354313544135451354613547135481354913550135511355213553135541355513556135571355813559135601356113562135631356413565135661356713568135691357013571135721357313574135751357613577135781357913580135811358213583135841358513586135871358813589135901359113592135931359413595135961359713598135991360013601136021360313604136051360613607136081360913610136111361213613136141361513616136171361813619136201362113622136231362413625136261362713628136291363013631136321363313634136351363613637136381363913640136411364213643136441364513646136471364813649136501365113652136531365413655136561365713658136591366013661136621366313664136651366613667136681366913670136711367213673136741367513676136771367813679136801368113682136831368413685136861368713688136891369013691136921369313694136951369613697136981369913700137011370213703137041370513706137071370813709137101371113712137131371413715137161371713718137191372013721137221372313724137251372613727137281372913730137311373213733137341373513736137371373813739137401374113742137431374413745137461374713748137491375013751137521375313754137551375613757137581375913760137611376213763137641376513766137671376813769137701377113772137731377413775137761377713778137791378013781137821378313784137851378613787137881378913790137911379213793137941379513796137971379813799138001380113802138031380413805138061380713808138091381013811138121381313814138151381613817138181381913820138211382213823138241382513826138271382813829138301383113832138331383413835138361383713838138391384013841138421384313844138451384613847138481384913850138511385213853138541385513856138571385813859138601386113862138631386413865138661386713868138691387013871138721387313874138751387613877138781387913880138811388213883138841388513886138871388813889138901389113892138931389413895138961389713898138991390013901139021390313904139051390613907139081390913910139111391213913139141391513916139171391813919139201392113922139231392413925139261392713928139291393013931139321393313934139351393613937139381393913940139411394213943139441394513946139471394813949139501395113952139531395413955139561395713958139591396013961139621396313964139651396613967139681396913970139711397213973139741397513976139771397813979139801398113982139831398413985139861398713988139891399013991139921399313994139951399613997139981399914000140011400214003140041400514006140071400814009140101401114012140131401414015140161401714018140191402014021140221402314024140251402614027140281402914030140311403214033140341403514036140371403814039140401404114042140431404414045140461404714048140491405014051140521405314054140551405614057140581405914060140611406214063140641406514066140671406814069140701407114072140731407414075140761407714078140791408014081140821408314084140851408614087140881408914090140911409214093140941409514096140971409814099141001410114102141031410414105141061410714108141091411014111141121411314114141151411614117141181411914120141211412214123141241412514126141271412814129141301413114132141331413414135141361413714138141391414014141141421414314144141451414614147141481414914150141511415214153141541415514156141571415814159141601416114162141631416414165141661416714168141691417014171141721417314174141751417614177141781417914180141811418214183141841418514186141871418814189141901419114192141931419414195141961419714198141991420014201142021420314204142051420614207142081420914210142111421214213142141421514216142171421814219142201422114222142231422414225142261422714228142291423014231142321423314234142351423614237142381423914240142411424214243142441424514246142471424814249142501425114252142531425414255142561425714258142591426014261142621426314264142651426614267142681426914270142711427214273142741427514276142771427814279142801428114282142831428414285142861428714288142891429014291142921429314294142951429614297142981429914300143011430214303143041430514306143071430814309143101431114312143131431414315143161431714318143191432014321143221432314324143251432614327143281432914330143311433214333143341433514336143371433814339143401434114342143431434414345143461434714348143491435014351143521435314354143551435614357143581435914360143611436214363143641436514366143671436814369143701437114372143731437414375143761437714378143791438014381143821438314384143851438614387143881438914390143911439214393143941439514396143971439814399144001440114402144031440414405144061440714408144091441014411144121441314414144151441614417144181441914420144211442214423144241442514426144271442814429144301443114432144331443414435144361443714438144391444014441144421444314444144451444614447144481444914450144511445214453144541445514456144571445814459144601446114462144631446414465144661446714468144691447014471144721447314474144751447614477144781447914480144811448214483144841448514486144871448814489144901449114492144931449414495144961449714498144991450014501145021450314504145051450614507145081450914510145111451214513145141451514516145171451814519145201452114522145231452414525145261452714528145291453014531145321453314534145351453614537145381453914540145411454214543145441454514546145471454814549145501455114552145531455414555145561455714558145591456014561145621456314564145651456614567145681456914570145711457214573145741457514576145771457814579145801458114582145831458414585145861458714588145891459014591145921459314594145951459614597145981459914600146011460214603146041460514606146071460814609146101461114612146131461414615146161461714618146191462014621146221462314624146251462614627146281462914630146311463214633146341463514636146371463814639146401464114642146431464414645146461464714648146491465014651146521465314654146551465614657146581465914660146611466214663146641466514666146671466814669146701467114672146731467414675146761467714678146791468014681146821468314684146851468614687146881468914690146911469214693146941469514696146971469814699147001470114702147031470414705147061470714708147091471014711147121471314714147151471614717147181471914720147211472214723147241472514726147271472814729147301473114732147331473414735147361473714738147391474014741147421474314744147451474614747147481474914750147511475214753147541475514756147571475814759147601476114762147631476414765147661476714768
  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. // <if classic>
  3054. afterComponentLayout: function(width, height, oldWidth, oldHeight) {
  3055. this.callParent([
  3056. width,
  3057. height,
  3058. oldWidth,
  3059. oldHeight
  3060. ]);
  3061. if (Ext.isIE9) {
  3062. this.handleResize();
  3063. }
  3064. },
  3065. // </if>
  3066. onElementResize: function(element, size) {
  3067. this.handleResize(size);
  3068. },
  3069. handleResize: function(size, instantly) {
  3070. var me = this,
  3071. el = me.element;
  3072. if (!(el && (size = size || el.getSize()) && size.width && size.height)) {
  3073. return;
  3074. }
  3075. me.resizeTimerId = Ext.undefer(me.resizeTimerId);
  3076. if (!instantly && me.resizeDelay) {
  3077. me.resizeTimerId = Ext.defer(me.handleResize, me.resizeDelay, me, [
  3078. size,
  3079. true
  3080. ]);
  3081. return;
  3082. }
  3083. me.size = size;
  3084. me.resizeHandler(size);
  3085. },
  3086. updateMinValue: function(minValue) {
  3087. var me = this;
  3088. me.interpolator.setDomain(minValue, me.getMaxValue());
  3089. if (!me.isConfiguring) {
  3090. me.render();
  3091. }
  3092. },
  3093. updateMaxValue: function(maxValue) {
  3094. var me = this;
  3095. me.interpolator.setDomain(me.getMinValue(), maxValue);
  3096. if (!me.isConfiguring) {
  3097. me.render();
  3098. }
  3099. },
  3100. updateAngleOffset: function(angleOffset, oldAngleOffset) {
  3101. var me = this,
  3102. animation = me.getAnimation();
  3103. me.fxAngleOffset = angleOffset;
  3104. if (me.isConfiguring) {
  3105. return;
  3106. }
  3107. if (animation.duration) {
  3108. me.animate(oldAngleOffset, angleOffset, animation.duration, me.easings[animation.easing], function(angleOffset) {
  3109. me.fxAngleOffset = angleOffset;
  3110. me.render();
  3111. });
  3112. } else {
  3113. me.render();
  3114. }
  3115. },
  3116. //<debug>
  3117. applyTrackStart: function(trackStart) {
  3118. if (trackStart < 0 || trackStart >= 360) {
  3119. Ext.raise("'trackStart' should be within [0, 360).");
  3120. }
  3121. return trackStart;
  3122. },
  3123. applyTrackLength: function(trackLength) {
  3124. if (trackLength <= 0 || trackLength > 360) {
  3125. Ext.raise("'trackLength' should be within (0, 360].");
  3126. }
  3127. return trackLength;
  3128. },
  3129. //</debug>
  3130. updateTrackStart: function(trackStart) {
  3131. var me = this;
  3132. if (!me.isConfiguring) {
  3133. me.render();
  3134. }
  3135. },
  3136. updateTrackLength: function(trackLength) {
  3137. var me = this;
  3138. me.interpolator.setRange(0, trackLength);
  3139. if (!me.isConfiguring) {
  3140. me.render();
  3141. }
  3142. },
  3143. applyPadding: function(padding) {
  3144. var ratio;
  3145. if (typeof padding === 'string') {
  3146. ratio = parseFloat(padding) / 100;
  3147. return function(x) {
  3148. return x * ratio;
  3149. };
  3150. }
  3151. return function() {
  3152. return padding;
  3153. };
  3154. },
  3155. updatePadding: function() {
  3156. if (!this.isConfiguring) {
  3157. this.render();
  3158. }
  3159. },
  3160. applyValue: function(value) {
  3161. var minValue = this.getMinValue(),
  3162. maxValue = this.getMaxValue();
  3163. return Math.min(Math.max(value, minValue), maxValue);
  3164. },
  3165. updateValue: function(value, oldValue) {
  3166. var me = this,
  3167. animation = me.getAnimation();
  3168. me.fxValue = value;
  3169. if (me.isConfiguring) {
  3170. return;
  3171. }
  3172. me.writeText();
  3173. if (animation.duration) {
  3174. me.animate(oldValue, value, animation.duration, me.easings[animation.easing], function(value) {
  3175. me.fxValue = value;
  3176. me.render();
  3177. });
  3178. } else {
  3179. me.render();
  3180. }
  3181. },
  3182. applyTextTpl: function(textTpl) {
  3183. if (textTpl && !textTpl.isTemplate) {
  3184. textTpl = new Ext.XTemplate(textTpl);
  3185. }
  3186. return textTpl;
  3187. },
  3188. applyTextOffset: function(offset) {
  3189. offset = offset || {};
  3190. offset.dx = offset.dx || 0;
  3191. offset.dy = offset.dy || 0;
  3192. return offset;
  3193. },
  3194. updateTextTpl: function() {
  3195. this.writeText();
  3196. if (!this.isConfiguring) {
  3197. this.centerText();
  3198. }
  3199. },
  3200. // text will be centered on first size
  3201. writeText: function(options) {
  3202. var me = this,
  3203. value = me.getValue(),
  3204. minValue = me.getMinValue(),
  3205. maxValue = me.getMaxValue(),
  3206. delta = maxValue - minValue,
  3207. textTpl = me.getTextTpl();
  3208. textTpl.overwrite(me.textElement, {
  3209. value: value,
  3210. percent: (value - minValue) / delta * 100,
  3211. minValue: minValue,
  3212. maxValue: maxValue,
  3213. delta: delta
  3214. });
  3215. },
  3216. centerText: function(cx, cy, sectorRegion, innerRadius, outerRadius) {
  3217. var textElement = this.textElement,
  3218. textAlign = this.getTextAlign(),
  3219. alignedRegion, textBox;
  3220. if (Ext.Number.isEqual(innerRadius, 0, 0.1) || sectorRegion.isOutOfBound({
  3221. x: cx,
  3222. y: cy
  3223. })) {
  3224. alignedRegion = textElement.getRegion().alignTo({
  3225. align: textAlign,
  3226. // align text region's center to sector region's center
  3227. target: sectorRegion
  3228. });
  3229. textElement.setLeft(alignedRegion.left);
  3230. textElement.setTop(alignedRegion.top);
  3231. } else {
  3232. textBox = textElement.getBox();
  3233. textElement.setLeft(cx - textBox.width / 2);
  3234. textElement.setTop(cy - textBox.height / 2);
  3235. }
  3236. },
  3237. camelCaseRe: /([a-z])([A-Z])/g,
  3238. /**
  3239. * @private
  3240. */
  3241. camelToHyphen: function(name) {
  3242. return name.replace(this.camelCaseRe, '$1-$2').toLowerCase();
  3243. },
  3244. applyTrackStyle: function(trackStyle) {
  3245. var me = this,
  3246. trackGradient;
  3247. trackStyle.innerRadius = me.getRadiusFn(trackStyle.innerRadius);
  3248. trackStyle.outerRadius = me.getRadiusFn(trackStyle.outerRadius);
  3249. if (Ext.isArray(trackStyle.fill)) {
  3250. trackGradient = me.getTrackGradient();
  3251. me.setGradientStops(trackGradient, trackStyle.fill);
  3252. trackStyle.fill = 'url(#' + trackGradient.dom.getAttribute('id') + ')';
  3253. }
  3254. return trackStyle;
  3255. },
  3256. updateTrackStyle: function(trackStyle) {
  3257. var me = this,
  3258. trackArc = Ext.fly(me.getTrackArc()),
  3259. name;
  3260. for (name in trackStyle) {
  3261. if (name in me.pathAttributes) {
  3262. trackArc.setStyle(me.camelToHyphen(name), trackStyle[name]);
  3263. } else {
  3264. trackArc.setStyle(name, trackStyle[name]);
  3265. }
  3266. }
  3267. },
  3268. applyValueStyle: function(valueStyle) {
  3269. var me = this,
  3270. valueGradient;
  3271. valueStyle.innerRadius = me.getRadiusFn(valueStyle.innerRadius);
  3272. valueStyle.outerRadius = me.getRadiusFn(valueStyle.outerRadius);
  3273. if (Ext.isArray(valueStyle.fill)) {
  3274. valueGradient = me.getValueGradient();
  3275. me.setGradientStops(valueGradient, valueStyle.fill);
  3276. valueStyle.fill = 'url(#' + valueGradient.dom.getAttribute('id') + ')';
  3277. }
  3278. return valueStyle;
  3279. },
  3280. updateValueStyle: function(valueStyle) {
  3281. var me = this,
  3282. valueArc = Ext.fly(me.getValueArc()),
  3283. name;
  3284. for (name in valueStyle) {
  3285. if (name in me.pathAttributes) {
  3286. valueArc.setStyle(me.camelToHyphen(name), valueStyle[name]);
  3287. } else {
  3288. valueArc.setStyle(name, valueStyle[name]);
  3289. }
  3290. }
  3291. },
  3292. /**
  3293. * @private
  3294. */
  3295. getRadiusFn: function(radius) {
  3296. var result, pos, ratio,
  3297. increment = 0;
  3298. if (Ext.isNumber(radius)) {
  3299. result = function() {
  3300. return radius;
  3301. };
  3302. } else if (Ext.isString(radius)) {
  3303. radius = radius.replace(/ /g, '');
  3304. ratio = parseFloat(radius) / 100;
  3305. pos = radius.search('%');
  3306. // E.g. '100% - 4'
  3307. if (pos < radius.length - 1) {
  3308. increment = parseFloat(radius.substr(pos + 1));
  3309. }
  3310. result = function(radius) {
  3311. return radius * ratio + increment;
  3312. };
  3313. result.ratio = ratio;
  3314. }
  3315. return result;
  3316. },
  3317. getSvg: function() {
  3318. var me = this,
  3319. svg = me.svg;
  3320. if (!svg) {
  3321. svg = me.svg = Ext.get(document.createElementNS(me.svgNS, 'svg'));
  3322. me.bodyElement.append(svg);
  3323. }
  3324. return svg;
  3325. },
  3326. getTrackArc: function() {
  3327. var me = this,
  3328. trackArc = me.trackArc;
  3329. if (!trackArc) {
  3330. trackArc = me.trackArc = document.createElementNS(me.svgNS, 'path');
  3331. me.getSvg().append(trackArc, true);
  3332. // Note: Ext.dom.Element.addCls doesn't work on SVG elements,
  3333. // as it simply assigns a class string to el.dom.className,
  3334. // which in case of SVG is no simple string:
  3335. // SVGAnimatedString {baseVal: "x-gauge-track", animVal: "x-gauge-track"}
  3336. trackArc.setAttribute('class', Ext.baseCSSPrefix + 'gauge-track');
  3337. }
  3338. return trackArc;
  3339. },
  3340. getValueArc: function() {
  3341. var me = this,
  3342. valueArc = me.valueArc;
  3343. me.getTrackArc();
  3344. // make sure the track arc is created first for proper draw order
  3345. if (!valueArc) {
  3346. valueArc = me.valueArc = document.createElementNS(me.svgNS, 'path');
  3347. me.getSvg().append(valueArc, true);
  3348. valueArc.setAttribute('class', Ext.baseCSSPrefix + 'gauge-value');
  3349. }
  3350. return valueArc;
  3351. },
  3352. applyNeedle: function(needle, oldNeedle) {
  3353. // Make sure the track and value elements have been already created,
  3354. // so that the needle element renders on top.
  3355. this.getValueArc();
  3356. return Ext.Factory.gaugeNeedle.update(oldNeedle, needle, this, 'createNeedle', 'needleDefaults');
  3357. },
  3358. createNeedle: function(config) {
  3359. return Ext.apply({
  3360. gauge: this
  3361. }, config);
  3362. },
  3363. getDefs: function() {
  3364. var me = this,
  3365. defs = me.defs;
  3366. if (!defs) {
  3367. defs = me.defs = Ext.get(document.createElementNS(me.svgNS, 'defs'));
  3368. me.getSvg().appendChild(defs);
  3369. }
  3370. return defs;
  3371. },
  3372. /**
  3373. * @private
  3374. */
  3375. setGradientSize: function(gradient, x1, y1, x2, y2) {
  3376. gradient.setAttribute('x1', x1);
  3377. gradient.setAttribute('y1', y1);
  3378. gradient.setAttribute('x2', x2);
  3379. gradient.setAttribute('y2', y2);
  3380. },
  3381. /**
  3382. * @private
  3383. */
  3384. resizeGradients: function(size) {
  3385. var me = this,
  3386. trackGradient = me.getTrackGradient(),
  3387. valueGradient = me.getValueGradient(),
  3388. x1 = 0,
  3389. y1 = size.height / 2,
  3390. x2 = size.width,
  3391. y2 = size.height / 2;
  3392. me.setGradientSize(trackGradient.dom, x1, y1, x2, y2);
  3393. me.setGradientSize(valueGradient.dom, x1, y1, x2, y2);
  3394. },
  3395. /**
  3396. * @private
  3397. */
  3398. setGradientStops: function(gradient, stops) {
  3399. var ln = stops.length,
  3400. i, stopCfg, stopEl;
  3401. while (gradient.firstChild) {
  3402. gradient.removeChild(gradient.firstChild);
  3403. }
  3404. for (i = 0; i < ln; i++) {
  3405. stopCfg = stops[i];
  3406. stopEl = document.createElementNS(this.svgNS, 'stop');
  3407. gradient.appendChild(stopEl);
  3408. stopEl.setAttribute('offset', stopCfg.offset);
  3409. stopEl.setAttribute('stop-color', stopCfg.color);
  3410. if ('opacity' in stopCfg) {
  3411. stopEl.setAttribute('stop-opacity', stopCfg.opacity);
  3412. }
  3413. }
  3414. },
  3415. getTrackGradient: function() {
  3416. var me = this,
  3417. trackGradient = me.trackGradient;
  3418. if (!trackGradient) {
  3419. trackGradient = me.trackGradient = Ext.get(document.createElementNS(me.svgNS, 'linearGradient'));
  3420. // Using absolute values for x1, y1, x2, y2 attributes.
  3421. trackGradient.dom.setAttribute('gradientUnits', 'userSpaceOnUse');
  3422. me.getDefs().appendChild(trackGradient);
  3423. Ext.get(trackGradient);
  3424. }
  3425. // assign unique ID
  3426. return trackGradient;
  3427. },
  3428. getValueGradient: function() {
  3429. var me = this,
  3430. valueGradient = me.valueGradient;
  3431. if (!valueGradient) {
  3432. valueGradient = me.valueGradient = Ext.get(document.createElementNS(me.svgNS, 'linearGradient'));
  3433. // Using absolute values for x1, y1, x2, y2 attributes.
  3434. valueGradient.dom.setAttribute('gradientUnits', 'userSpaceOnUse');
  3435. me.getDefs().appendChild(valueGradient);
  3436. Ext.get(valueGradient);
  3437. }
  3438. // assign unique ID
  3439. return valueGradient;
  3440. },
  3441. getArcPoint: function(centerX, centerY, radius, degrees) {
  3442. var radians = degrees / 180 * Math.PI;
  3443. return [
  3444. centerX + radius * Math.cos(radians),
  3445. centerY + radius * Math.sin(radians)
  3446. ];
  3447. },
  3448. isCircle: function(startAngle, endAngle) {
  3449. return Ext.Number.isEqual(Math.abs(endAngle - startAngle), 360, 0.001);
  3450. },
  3451. getArcPath: function(centerX, centerY, innerRadius, outerRadius, startAngle, endAngle, round) {
  3452. var me = this,
  3453. isCircle = me.isCircle(startAngle, endAngle),
  3454. // It's not possible to draw a circle using arcs.
  3455. endAngle = endAngle - 0.01,
  3456. // eslint-disable-line no-redeclare
  3457. innerStartPoint = me.getArcPoint(centerX, centerY, innerRadius, startAngle),
  3458. innerEndPoint = me.getArcPoint(centerX, centerY, innerRadius, endAngle),
  3459. outerStartPoint = me.getArcPoint(centerX, centerY, outerRadius, startAngle),
  3460. outerEndPoint = me.getArcPoint(centerX, centerY, outerRadius, endAngle),
  3461. large = endAngle - startAngle <= 180 ? 0 : 1,
  3462. path = [
  3463. 'M',
  3464. innerStartPoint[0],
  3465. innerStartPoint[1],
  3466. 'A',
  3467. innerRadius,
  3468. innerRadius,
  3469. 0,
  3470. large,
  3471. 1,
  3472. innerEndPoint[0],
  3473. innerEndPoint[1]
  3474. ],
  3475. capRadius = (outerRadius - innerRadius) / 2;
  3476. if (isCircle) {
  3477. path.push('M', outerEndPoint[0], outerEndPoint[1]);
  3478. } else {
  3479. if (round) {
  3480. path.push('A', capRadius, capRadius, 0, 0, 0, outerEndPoint[0], outerEndPoint[1]);
  3481. } else {
  3482. path.push('L', outerEndPoint[0], outerEndPoint[1]);
  3483. }
  3484. }
  3485. path.push('A', outerRadius, outerRadius, 0, large, 0, outerStartPoint[0], outerStartPoint[1]);
  3486. if (round && !isCircle) {
  3487. path.push('A', capRadius, capRadius, 0, 0, 0, innerStartPoint[0], innerStartPoint[1]);
  3488. }
  3489. path.push('Z');
  3490. return path.join(' ');
  3491. },
  3492. resizeHandler: function(size) {
  3493. var me = this,
  3494. svg = me.getSvg();
  3495. svg.setSize(size);
  3496. me.resizeGradients(size);
  3497. me.render();
  3498. },
  3499. /**
  3500. * @private
  3501. * Creates a linear interpolator function that itself has a few methods:
  3502. * - `setDomain(from, to)`
  3503. * - `setRange(from, to)`
  3504. * - `getDomain` - returns the domain as a [from, to] array
  3505. * - `getRange` - returns the range as a [from, to] array
  3506. * @param {Boolean} [rangeCheck=false]
  3507. * Whether to allow out of bounds values for domain and range.
  3508. * @return {Function} The interpolator function:
  3509. * `interpolator(domainValue, isInvert)`.
  3510. * If the `isInvert` parameter is `true`, the start of domain will correspond
  3511. * to the end of range. This is useful, for example, when you want to render
  3512. * increasing domain values counter-clockwise instead of clockwise.
  3513. */
  3514. createInterpolator: function(rangeCheck) {
  3515. var domainStart = 0,
  3516. domainDelta = 1,
  3517. rangeStart = 0,
  3518. rangeEnd = 1,
  3519. interpolator = function(x, invert) {
  3520. var t = 0;
  3521. if (domainDelta) {
  3522. t = (x - domainStart) / domainDelta;
  3523. if (rangeCheck) {
  3524. t = Math.max(0, t);
  3525. t = Math.min(1, t);
  3526. }
  3527. if (invert) {
  3528. t = 1 - t;
  3529. }
  3530. }
  3531. return (1 - t) * rangeStart + t * rangeEnd;
  3532. };
  3533. interpolator.setDomain = function(a, b) {
  3534. domainStart = a;
  3535. domainDelta = b - a;
  3536. return this;
  3537. };
  3538. interpolator.setRange = function(a, b) {
  3539. rangeStart = a;
  3540. rangeEnd = b;
  3541. return this;
  3542. };
  3543. interpolator.getDomain = function() {
  3544. return [
  3545. domainStart,
  3546. domainStart + domainDelta
  3547. ];
  3548. };
  3549. interpolator.getRange = function() {
  3550. return [
  3551. rangeStart,
  3552. rangeEnd
  3553. ];
  3554. };
  3555. return interpolator;
  3556. },
  3557. applyAnimation: function(animation) {
  3558. if (true === animation) {
  3559. animation = {};
  3560. } else if (false === animation) {
  3561. animation = {
  3562. duration: 0
  3563. };
  3564. }
  3565. if (!('duration' in animation)) {
  3566. animation.duration = 1000;
  3567. }
  3568. if (!(animation.easing in this.easings)) {
  3569. animation.easing = 'out';
  3570. }
  3571. return animation;
  3572. },
  3573. updateAnimation: function() {
  3574. this.stopAnimation();
  3575. },
  3576. /**
  3577. * @private
  3578. * @param {Number} from
  3579. * @param {Number} to
  3580. * @param {Number} duration
  3581. * @param {Function} easing
  3582. * @param {Function} fn Function to execute on every frame of animation.
  3583. * The function takes a single parameter - the value in the [from, to]
  3584. * range, interpolated based on current time and easing function.
  3585. * With certain easings, the value may overshoot the range slighly.
  3586. * @param {Object} scope
  3587. */
  3588. animate: function(from, to, duration, easing, fn, scope) {
  3589. var me = this,
  3590. start = Ext.now(),
  3591. interpolator = me.createInterpolator().setRange(from, to);
  3592. function frame() {
  3593. var now = Ext.AnimationQueue.frameStartTime,
  3594. t = Math.min(now - start, duration) / duration,
  3595. value = interpolator(easing(t));
  3596. if (scope) {
  3597. if (typeof fn === 'string') {
  3598. scope[fn].call(scope, value);
  3599. } else {
  3600. fn.call(scope, value);
  3601. }
  3602. } else {
  3603. fn(value);
  3604. }
  3605. if (t >= 1) {
  3606. Ext.AnimationQueue.stop(frame, scope);
  3607. me.fx = null;
  3608. }
  3609. }
  3610. me.stopAnimation();
  3611. Ext.AnimationQueue.start(frame, scope);
  3612. me.fx = {
  3613. frame: frame,
  3614. scope: scope
  3615. };
  3616. },
  3617. /**
  3618. * Stops the current {@link #value} or {@link #angleOffset} animation.
  3619. */
  3620. stopAnimation: function() {
  3621. var me = this;
  3622. if (me.fx) {
  3623. Ext.AnimationQueue.stop(me.fx.frame, me.fx.scope);
  3624. me.fx = null;
  3625. }
  3626. },
  3627. unitCircleExtrema: {
  3628. 0: [
  3629. 1,
  3630. 0
  3631. ],
  3632. 90: [
  3633. 0,
  3634. 1
  3635. ],
  3636. 180: [
  3637. -1,
  3638. 0
  3639. ],
  3640. 270: [
  3641. 0,
  3642. -1
  3643. ],
  3644. 360: [
  3645. 1,
  3646. 0
  3647. ],
  3648. 450: [
  3649. 0,
  3650. 1
  3651. ],
  3652. 540: [
  3653. -1,
  3654. 0
  3655. ],
  3656. 630: [
  3657. 0,
  3658. -1
  3659. ]
  3660. },
  3661. /**
  3662. * @private
  3663. */
  3664. getUnitSectorExtrema: function(startAngle, lengthAngle) {
  3665. var extrema = this.unitCircleExtrema,
  3666. points = [],
  3667. angle;
  3668. for (angle in extrema) {
  3669. if (angle > startAngle && angle < startAngle + lengthAngle) {
  3670. points.push(extrema[angle]);
  3671. }
  3672. }
  3673. return points;
  3674. },
  3675. /**
  3676. * @private
  3677. * Given a rect with a known width and height, find the maximum radius of the donut
  3678. * sector that can fit into it, as well as the center point of such a sector.
  3679. * The end and start angles of the sector are also known, as well as the relationship
  3680. * between the inner and outer radii.
  3681. */
  3682. fitSectorInRect: function(width, height, startAngle, lengthAngle, ratio) {
  3683. if (Ext.Number.isEqual(lengthAngle, 360, 0.001)) {
  3684. return {
  3685. cx: width / 2,
  3686. cy: height / 2,
  3687. radius: Math.min(width, height) / 2,
  3688. region: new Ext.util.Region(0, width, height, 0)
  3689. };
  3690. }
  3691. // eslint-disable-next-line vars-on-top
  3692. var me = this,
  3693. points, xx, yy, minX, maxX, minY, maxY,
  3694. cache = me.fitSectorInRectCache,
  3695. sameAngles = cache.startAngle === startAngle && cache.lengthAngle === lengthAngle;
  3696. if (sameAngles) {
  3697. minX = cache.minX;
  3698. maxX = cache.maxX;
  3699. minY = cache.minY;
  3700. maxY = cache.maxY;
  3701. } else {
  3702. points = me.getUnitSectorExtrema(startAngle, lengthAngle).concat([
  3703. // start angle outer radius point
  3704. me.getArcPoint(0, 0, 1, startAngle),
  3705. // start angle inner radius point
  3706. me.getArcPoint(0, 0, ratio, startAngle),
  3707. // end angle outer radius point
  3708. me.getArcPoint(0, 0, 1, startAngle + lengthAngle),
  3709. // end angle inner radius point
  3710. me.getArcPoint(0, 0, ratio, startAngle + lengthAngle)
  3711. ]);
  3712. xx = points.map(function(point) {
  3713. return point[0];
  3714. });
  3715. yy = points.map(function(point) {
  3716. return point[1];
  3717. });
  3718. // The bounding box of a unit sector with the given properties.
  3719. minX = Math.min.apply(null, xx);
  3720. maxX = Math.max.apply(null, xx);
  3721. minY = Math.min.apply(null, yy);
  3722. maxY = Math.max.apply(null, yy);
  3723. cache.startAngle = startAngle;
  3724. cache.lengthAngle = lengthAngle;
  3725. cache.minX = minX;
  3726. cache.maxX = maxX;
  3727. cache.minY = minY;
  3728. cache.maxY = maxY;
  3729. }
  3730. // eslint-disable-next-line vars-on-top, one-var
  3731. var sectorWidth = maxX - minX,
  3732. sectorHeight = maxY - minY,
  3733. scaleX = width / sectorWidth,
  3734. scaleY = height / sectorHeight,
  3735. scale = Math.min(scaleX, scaleY),
  3736. // Region constructor takes: top, right, bottom, left.
  3737. sectorRegion = new Ext.util.Region(minY * scale, maxX * scale, maxY * scale, minX * scale),
  3738. rectRegion = new Ext.util.Region(0, width, height, 0),
  3739. alignedRegion = sectorRegion.alignTo({
  3740. align: 'c-c',
  3741. // align sector region's center to rect region's center
  3742. target: rectRegion
  3743. }),
  3744. dx = alignedRegion.left - minX * scale,
  3745. dy = alignedRegion.top - minY * scale;
  3746. return {
  3747. cx: dx,
  3748. cy: dy,
  3749. radius: scale,
  3750. region: alignedRegion
  3751. };
  3752. },
  3753. /**
  3754. * @private
  3755. */
  3756. fitSectorInPaddedRect: function(width, height, padding, startAngle, lengthAngle, ratio) {
  3757. var result = this.fitSectorInRect(width - padding * 2, height - padding * 2, startAngle, lengthAngle, ratio);
  3758. result.cx += padding;
  3759. result.cy += padding;
  3760. result.region.translateBy(padding, padding);
  3761. return result;
  3762. },
  3763. /**
  3764. * @private
  3765. */
  3766. normalizeAngle: function(angle) {
  3767. return (angle % 360 + 360) % 360;
  3768. },
  3769. render: function() {
  3770. if (!this.size) {
  3771. return;
  3772. }
  3773. // eslint-disable-next-line vars-on-top
  3774. var me = this,
  3775. textOffset = me.getTextOffset(),
  3776. trackArc = me.getTrackArc(),
  3777. valueArc = me.getValueArc(),
  3778. needle = me.getNeedle(),
  3779. clockwise = me.getClockwise(),
  3780. value = me.fxValue,
  3781. angleOffset = me.fxAngleOffset,
  3782. trackLength = me.getTrackLength(),
  3783. width = me.size.width,
  3784. height = me.size.height,
  3785. paddingFn = me.getPadding(),
  3786. padding = paddingFn(Math.min(width, height)),
  3787. // in the range of [0, 360)
  3788. trackStart = me.normalizeAngle(me.getTrackStart() + angleOffset),
  3789. // in the range of (0, 720)
  3790. trackEnd = trackStart + trackLength,
  3791. valueLength = me.interpolator(value),
  3792. trackStyle = me.getTrackStyle(),
  3793. valueStyle = me.getValueStyle(),
  3794. sector = me.fitSectorInPaddedRect(width, height, padding, trackStart, trackLength, trackStyle.innerRadius.ratio),
  3795. cx = sector.cx,
  3796. cy = sector.cy,
  3797. radius = sector.radius,
  3798. trackInnerRadius = Math.max(0, trackStyle.innerRadius(radius)),
  3799. trackOuterRadius = Math.max(0, trackStyle.outerRadius(radius)),
  3800. valueInnerRadius = Math.max(0, valueStyle.innerRadius(radius)),
  3801. valueOuterRadius = Math.max(0, valueStyle.outerRadius(radius)),
  3802. trackPath = me.getArcPath(cx, cy, trackInnerRadius, trackOuterRadius, trackStart, trackEnd, trackStyle.round),
  3803. valuePath = me.getArcPath(cx, cy, valueInnerRadius, valueOuterRadius, clockwise ? trackStart : trackEnd - valueLength, clockwise ? trackStart + valueLength : trackEnd, valueStyle.round);
  3804. me.centerText(cx + textOffset.dx, cy + textOffset.dy, sector.region, trackInnerRadius, trackOuterRadius);
  3805. trackArc.setAttribute('d', trackPath);
  3806. valueArc.setAttribute('d', valuePath);
  3807. if (needle) {
  3808. needle.setRadius(radius);
  3809. needle.setTransform(cx, cy, -90 + trackStart + valueLength);
  3810. }
  3811. me.fireEvent('render', me);
  3812. }
  3813. });
  3814. Ext.define('Ext.ux.gauge.needle.Arrow', {
  3815. extend: 'Ext.ux.gauge.needle.Abstract',
  3816. alias: 'gauge.needle.arrow',
  3817. config: {
  3818. path: function(ir, or) {
  3819. 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" : '';
  3820. }
  3821. }
  3822. });
  3823. Ext.define('Ext.ux.gauge.needle.Diamond', {
  3824. extend: 'Ext.ux.gauge.needle.Abstract',
  3825. alias: 'gauge.needle.diamond',
  3826. config: {
  3827. path: function(ir, or) {
  3828. return or - ir > 10 ? 'M0,' + ir + ' L-4,' + (ir + 5) + ' L0,' + or + ' L4,' + (ir + 5) + ' Z' : '';
  3829. }
  3830. }
  3831. });
  3832. Ext.define('Ext.ux.gauge.needle.Rectangle', {
  3833. extend: 'Ext.ux.gauge.needle.Abstract',
  3834. alias: 'gauge.needle.rectangle',
  3835. config: {
  3836. path: function(ir, or) {
  3837. return or - ir > 10 ? "M-2," + ir + " L2," + ir + " L2," + or + " L-2," + or + " Z" : '';
  3838. }
  3839. }
  3840. });
  3841. Ext.define('Ext.ux.gauge.needle.Spike', {
  3842. extend: 'Ext.ux.gauge.needle.Abstract',
  3843. alias: 'gauge.needle.spike',
  3844. config: {
  3845. path: function(ir, or) {
  3846. return or - ir > 10 ? "M0," + (ir + 5) + " L-4," + ir + " L0," + or + " L4," + ir + " Z" : '';
  3847. }
  3848. }
  3849. });
  3850. Ext.define('Ext.ux.gauge.needle.Wedge', {
  3851. extend: 'Ext.ux.gauge.needle.Abstract',
  3852. alias: 'gauge.needle.wedge',
  3853. config: {
  3854. path: function(ir, or) {
  3855. return or - ir > 10 ? "M-4," + ir + " L0," + or + " L4," + ir + " Z" : '';
  3856. }
  3857. }
  3858. });
  3859. /**
  3860. * A ratings picker based on `Ext.Gadget`.
  3861. *
  3862. * @example
  3863. * Ext.create({
  3864. * xtype: 'rating',
  3865. * renderTo: Ext.getBody(),
  3866. * listeners: {
  3867. * change: function (picker, value) {
  3868. * console.log('Rating ' + value);
  3869. * }
  3870. * }
  3871. * });
  3872. */
  3873. Ext.define('Ext.ux.rating.Picker', {
  3874. extend: 'Ext.Gadget',
  3875. xtype: 'rating',
  3876. focusable: true,
  3877. /*
  3878. * The "cachedConfig" block is basically the same as "config" except that these
  3879. * values are applied specially to the first instance of the class. After processing
  3880. * these configs, the resulting values are stored on the class `prototype` and the
  3881. * template DOM element also reflects these default values.
  3882. */
  3883. cachedConfig: {
  3884. /**
  3885. * @cfg {String} [family]
  3886. * The CSS `font-family` to use for displaying the `{@link #glyphs}`.
  3887. */
  3888. family: 'monospace',
  3889. /**
  3890. * @cfg {String/String[]/Number[]} [glyphs]
  3891. * Either a string containing the two glyph characters, or an array of two strings
  3892. * containing the individual glyph characters or an array of two numbers with the
  3893. * character codes for the individual glyphs.
  3894. *
  3895. * For example:
  3896. *
  3897. * @example
  3898. * Ext.create({
  3899. * xtype: 'rating',
  3900. * renderTo: Ext.getBody(),
  3901. * glyphs: [ 9671, 9670 ], // '◇◆',
  3902. * listeners: {
  3903. * change: function (picker, value) {
  3904. * console.log('Rating ' + value);
  3905. * }
  3906. * }
  3907. * });
  3908. */
  3909. glyphs: '☆★',
  3910. /**
  3911. * @cfg {Number} [minimum=1]
  3912. * The minimum allowed `{@link #value}` (rating).
  3913. */
  3914. minimum: 1,
  3915. /**
  3916. * @cfg {Number} [limit]
  3917. * The maximum allowed `{@link #value}` (rating).
  3918. */
  3919. limit: 5,
  3920. /**
  3921. * @cfg {String/Object} [overStyle]
  3922. * Optional styles to apply to the rating glyphs when `{@link #trackOver}` is
  3923. * enabled.
  3924. */
  3925. overStyle: null,
  3926. /**
  3927. * @cfg {Number} [rounding=1]
  3928. * The rounding to apply to values. Common choices are 0.5 (for half-steps) or
  3929. * 0.25 (for quarter steps).
  3930. */
  3931. rounding: 1,
  3932. /**
  3933. * @cfg {String} [scale="125%"]
  3934. * The CSS `font-size` to apply to the glyphs. This value defaults to 125% because
  3935. * glyphs in the stock font tend to be too small. When using specially designed
  3936. * "icon fonts" you may want to set this to 100%.
  3937. */
  3938. scale: '125%',
  3939. /**
  3940. * @cfg {String/Object} [selectedStyle]
  3941. * Optional styles to apply to the rating value glyphs.
  3942. */
  3943. selectedStyle: null,
  3944. /**
  3945. * @cfg {Object/String/String[]/Ext.XTemplate/Function} tip
  3946. * A template or a function that produces the tooltip text. The `Object`, `String`
  3947. * and `String[]` forms are converted to an `Ext.XTemplate`. If a function is given,
  3948. * it will be called with an object parameter and should return the tooltip text.
  3949. * The object contains these properties:
  3950. *
  3951. * - component: The rating component requesting the tooltip.
  3952. * - tracking: The current value under the mouse cursor.
  3953. * - trackOver: The value of the `{@link #trackOver}` config.
  3954. * - value: The current value.
  3955. *
  3956. * Templates can use these properties to generate the proper text.
  3957. */
  3958. tip: null,
  3959. /**
  3960. * @cfg {Boolean} [trackOver=true]
  3961. * Determines if mouse movements should temporarily update the displayed value.
  3962. * The actual `value` is only updated on `click` but this rather acts as the
  3963. * "preview" of the value prior to click.
  3964. */
  3965. trackOver: true,
  3966. /**
  3967. * @cfg {Number} value
  3968. * The rating value. This value is bounded by `minimum` and `limit` and is also
  3969. * adjusted by the `rounding`.
  3970. */
  3971. value: null,
  3972. //---------------------------------------------------------------------
  3973. // Private configs
  3974. /**
  3975. * @cfg {String} tooltipText
  3976. * The current tooltip text. This value is set into the DOM by the updater (hence
  3977. * only when it changes). This is intended for use by the tip manager
  3978. * (`{@link Ext.tip.QuickTipManager}`). Developers should never need to set this
  3979. * config since it is handled by virtue of setting other configs (such as the
  3980. * {@link #tooltip} or the {@link #value}.).
  3981. * @private
  3982. */
  3983. tooltipText: null,
  3984. /**
  3985. * @cfg {Number} trackingValue
  3986. * This config is used to when `trackOver` is `true` and represents the tracked
  3987. * value. This config is maintained by our `mousemove` handler. This should not
  3988. * need to be set directly by user code.
  3989. * @private
  3990. */
  3991. trackingValue: null
  3992. },
  3993. config: {
  3994. /**
  3995. * @cfg {Boolean/Object} [animate=false]
  3996. * Specifies an animation to use when changing the `{@link #value}`. When setting
  3997. * this config, it is probably best to set `{@link #trackOver}` to `false`.
  3998. */
  3999. animate: null
  4000. },
  4001. // This object describes our element tree from the root.
  4002. element: {
  4003. cls: 'u' + Ext.baseCSSPrefix + 'rating-picker',
  4004. // Since we are replacing the entire "element" tree, we have to assign this
  4005. // "reference" as would our base class.
  4006. reference: 'element',
  4007. children: [
  4008. {
  4009. reference: 'innerEl',
  4010. cls: 'u' + Ext.baseCSSPrefix + 'rating-picker-inner',
  4011. listeners: {
  4012. click: 'onClick',
  4013. mousemove: 'onMouseMove',
  4014. mouseenter: 'onMouseEnter',
  4015. mouseleave: 'onMouseLeave'
  4016. },
  4017. children: [
  4018. {
  4019. reference: 'valueEl',
  4020. cls: 'u' + Ext.baseCSSPrefix + 'rating-picker-value'
  4021. },
  4022. {
  4023. reference: 'trackerEl',
  4024. cls: 'u' + Ext.baseCSSPrefix + 'rating-picker-tracker'
  4025. }
  4026. ]
  4027. }
  4028. ]
  4029. },
  4030. // Tell the Binding system to default to our "value" config.
  4031. defaultBindProperty: 'value',
  4032. // Enable two-way data binding for the "value" config.
  4033. twoWayBindable: 'value',
  4034. overCls: 'u' + Ext.baseCSSPrefix + 'rating-picker-over',
  4035. trackOverCls: 'u' + Ext.baseCSSPrefix + 'rating-picker-track-over',
  4036. //-------------------------------------------------------------------------
  4037. // Config Appliers
  4038. applyGlyphs: function(value) {
  4039. if (typeof value === 'string') {
  4040. //<debug>
  4041. if (value.length !== 2) {
  4042. Ext.raise('Expected 2 characters for "glyphs" not "' + value + '".');
  4043. }
  4044. //</debug>
  4045. value = [
  4046. value.charAt(0),
  4047. value.charAt(1)
  4048. ];
  4049. } else if (typeof value[0] === 'number') {
  4050. value = [
  4051. String.fromCharCode(value[0]),
  4052. String.fromCharCode(value[1])
  4053. ];
  4054. }
  4055. return value;
  4056. },
  4057. applyOverStyle: function(style) {
  4058. this.trackerEl.applyStyles(style);
  4059. },
  4060. applySelectedStyle: function(style) {
  4061. this.valueEl.applyStyles(style);
  4062. },
  4063. applyTip: function(tip) {
  4064. if (tip && typeof tip !== 'function') {
  4065. if (!tip.isTemplate) {
  4066. tip = new Ext.XTemplate(tip);
  4067. }
  4068. tip = tip.apply.bind(tip);
  4069. }
  4070. return tip;
  4071. },
  4072. applyTrackingValue: function(value) {
  4073. return this.applyValue(value);
  4074. },
  4075. // same rounding as normal value
  4076. applyValue: function(v) {
  4077. var rounding, limit, min;
  4078. if (v !== null) {
  4079. rounding = this.getRounding();
  4080. limit = this.getLimit();
  4081. min = this.getMinimum();
  4082. v = Math.round(Math.round(v / rounding) * rounding * 1000) / 1000;
  4083. v = (v < min) ? min : (v > limit ? limit : v);
  4084. }
  4085. return v;
  4086. },
  4087. //-------------------------------------------------------------------------
  4088. // Event Handlers
  4089. onClick: function(event) {
  4090. var value = this.valueFromEvent(event);
  4091. this.setValue(value);
  4092. },
  4093. onMouseEnter: function() {
  4094. this.element.addCls(this.overCls);
  4095. },
  4096. onMouseLeave: function() {
  4097. this.element.removeCls(this.overCls);
  4098. },
  4099. onMouseMove: function(event) {
  4100. var value = this.valueFromEvent(event);
  4101. this.setTrackingValue(value);
  4102. },
  4103. //-------------------------------------------------------------------------
  4104. // Config Updaters
  4105. updateFamily: function(family) {
  4106. this.element.setStyle('fontFamily', "'" + family + "'");
  4107. },
  4108. updateGlyphs: function() {
  4109. this.refreshGlyphs();
  4110. },
  4111. updateLimit: function() {
  4112. this.refreshGlyphs();
  4113. },
  4114. updateScale: function(size) {
  4115. this.element.setStyle('fontSize', size);
  4116. },
  4117. updateTip: function() {
  4118. this.refreshTip();
  4119. },
  4120. updateTooltipText: function(text) {
  4121. this.setTooltip(text);
  4122. },
  4123. // modern only (replaced by classic override)
  4124. updateTrackingValue: function(value) {
  4125. var me = this,
  4126. trackerEl = me.trackerEl,
  4127. newWidth = me.valueToPercent(value);
  4128. trackerEl.setStyle('width', newWidth);
  4129. me.refreshTip();
  4130. },
  4131. updateTrackOver: function(trackOver) {
  4132. this.element.toggleCls(this.trackOverCls, trackOver);
  4133. },
  4134. updateValue: function(value, oldValue) {
  4135. var me = this,
  4136. animate = me.getAnimate(),
  4137. valueEl = me.valueEl,
  4138. newWidth = me.valueToPercent(value),
  4139. column, record;
  4140. if (me.isConfiguring || !animate) {
  4141. valueEl.setStyle('width', newWidth);
  4142. } else {
  4143. valueEl.stopAnimation();
  4144. valueEl.animate(Ext.merge({
  4145. from: {
  4146. width: me.valueToPercent(oldValue)
  4147. },
  4148. to: {
  4149. width: newWidth
  4150. }
  4151. }, animate));
  4152. }
  4153. me.refreshTip();
  4154. if (!me.isConfiguring) {
  4155. // Since we are (re)configured many times as we are used in a grid cell, we
  4156. // avoid firing the change event unless there are listeners.
  4157. if (me.hasListeners.change) {
  4158. me.fireEvent('change', me, value, oldValue);
  4159. }
  4160. column = me.getWidgetColumn && me.getWidgetColumn();
  4161. record = column && me.getWidgetRecord && me.getWidgetRecord();
  4162. if (record && column.dataIndex) {
  4163. // When used in a widgetcolumn, we should update the backing field. The
  4164. // linkages will be cleared as we are being recycled, so this will only
  4165. // reach this line when we are properly attached to a record and the
  4166. // change is coming from the user (or a call to setValue).
  4167. record.set(column.dataIndex, value);
  4168. }
  4169. }
  4170. },
  4171. //-------------------------------------------------------------------------
  4172. // Config System Optimizations
  4173. //
  4174. // These are to deal with configs that combine to determine what should be
  4175. // rendered in the DOM. For example, "glyphs" and "limit" must both be known
  4176. // to render the proper text nodes. The "tip" and "value" likewise are
  4177. // used to update the tooltipText.
  4178. //
  4179. // To avoid multiple updates to the DOM (one for each config), we simply mark
  4180. // the rendering as invalid and post-process these flags on the tail of any
  4181. // bulk updates.
  4182. afterCachedConfig: function() {
  4183. // Now that we are done setting up the initial values we need to refresh the
  4184. // DOM before we allow Ext.Widget's implementation to cloneNode on it.
  4185. this.refresh();
  4186. return this.callParent(arguments);
  4187. },
  4188. initConfig: function(instanceConfig) {
  4189. this.isConfiguring = true;
  4190. this.callParent([
  4191. instanceConfig
  4192. ]);
  4193. // The firstInstance will already have refreshed the DOM (in afterCacheConfig)
  4194. // but all instances beyond the first need to refresh if they have custom values
  4195. // for one or more configs that affect the DOM (such as "glyphs" and "limit").
  4196. this.refresh();
  4197. },
  4198. setConfig: function() {
  4199. var me = this;
  4200. // Since we could be updating multiple configs, save any updates that need
  4201. // multiple values for afterwards.
  4202. me.isReconfiguring = true;
  4203. me.callParent(arguments);
  4204. me.isReconfiguring = false;
  4205. // Now that all new values are set, we can refresh the DOM.
  4206. me.refresh();
  4207. return me;
  4208. },
  4209. //-------------------------------------------------------------------------
  4210. privates: {
  4211. /**
  4212. * This method returns the DOM text node into which glyphs are placed.
  4213. * @param {HTMLElement} dom The DOM node parent of the text node.
  4214. * @return {HTMLElement} The text node.
  4215. * @private
  4216. */
  4217. getGlyphTextNode: function(dom) {
  4218. var node = dom.lastChild;
  4219. // We want all our text nodes to be at the end of the child list, most
  4220. // especially the text node on the innerEl. That text node affects the
  4221. // default left/right position of our absolutely positioned child divs
  4222. // (trackerEl and valueEl).
  4223. if (!node || node.nodeType !== 3) {
  4224. node = dom.ownerDocument.createTextNode('');
  4225. dom.appendChild(node);
  4226. }
  4227. return node;
  4228. },
  4229. getTooltipData: function() {
  4230. var me = this;
  4231. return {
  4232. component: me,
  4233. tracking: me.getTrackingValue(),
  4234. trackOver: me.getTrackOver(),
  4235. value: me.getValue()
  4236. };
  4237. },
  4238. /**
  4239. * Forcibly refreshes both glyph and tooltip rendering.
  4240. * @private
  4241. */
  4242. refresh: function() {
  4243. var me = this;
  4244. if (me.invalidGlyphs) {
  4245. me.refreshGlyphs(true);
  4246. }
  4247. if (me.invalidTip) {
  4248. me.refreshTip(true);
  4249. }
  4250. },
  4251. /**
  4252. * Refreshes the glyph text rendering unless we are currently performing a
  4253. * bulk config change (initConfig or setConfig).
  4254. * @param {Boolean} now Pass `true` to force the refresh to happen now.
  4255. * @private
  4256. */
  4257. refreshGlyphs: function(now) {
  4258. var me = this,
  4259. later = !now && (me.isConfiguring || me.isReconfiguring),
  4260. el, glyphs, limit, on, off, trackerEl, valueEl;
  4261. if (!later) {
  4262. el = me.getGlyphTextNode(me.innerEl.dom);
  4263. valueEl = me.getGlyphTextNode(me.valueEl.dom);
  4264. trackerEl = me.getGlyphTextNode(me.trackerEl.dom);
  4265. glyphs = me.getGlyphs();
  4266. limit = me.getLimit();
  4267. for (on = off = ''; limit--; ) {
  4268. off += glyphs[0];
  4269. on += glyphs[1];
  4270. }
  4271. el.nodeValue = off;
  4272. valueEl.nodeValue = on;
  4273. trackerEl.nodeValue = on;
  4274. }
  4275. me.invalidGlyphs = later;
  4276. },
  4277. /**
  4278. * Refreshes the tooltip text rendering unless we are currently performing a
  4279. * bulk config change (initConfig or setConfig).
  4280. * @param {Boolean} now Pass `true` to force the refresh to happen now.
  4281. * @private
  4282. */
  4283. refreshTip: function(now) {
  4284. var me = this,
  4285. later = !now && (me.isConfiguring || me.isReconfiguring),
  4286. data, text, tooltip;
  4287. if (!later) {
  4288. tooltip = me.getTip();
  4289. if (tooltip) {
  4290. data = me.getTooltipData();
  4291. text = tooltip(data);
  4292. me.setTooltipText(text);
  4293. }
  4294. }
  4295. me.invalidTip = later;
  4296. },
  4297. /**
  4298. * Convert the coordinates of the given `Event` into a rating value.
  4299. * @param {Ext.event.Event} event The event.
  4300. * @return {Number} The rating based on the given event coordinates.
  4301. * @private
  4302. */
  4303. valueFromEvent: function(event) {
  4304. var me = this,
  4305. el = me.innerEl,
  4306. ex = event.getX(),
  4307. rounding = me.getRounding(),
  4308. cx = el.getX(),
  4309. x = ex - cx,
  4310. w = el.getWidth(),
  4311. limit = me.getLimit(),
  4312. v;
  4313. if (me.getInherited().rtl) {
  4314. x = w - x;
  4315. }
  4316. v = x / w * limit;
  4317. // We have to round up here so that the area we are over is considered
  4318. // the value.
  4319. v = Math.ceil(v / rounding) * rounding;
  4320. return v;
  4321. },
  4322. /**
  4323. * Convert the given rating into a width percentage.
  4324. * @param {Number} value The rating value to convert.
  4325. * @return {String} The width percentage to represent the given value.
  4326. * @private
  4327. */
  4328. valueToPercent: function(value) {
  4329. value = (value / this.getLimit()) * 100;
  4330. return value + '%';
  4331. }
  4332. }
  4333. });
  4334. /**
  4335. * @class Ext.ux.rating.Picker
  4336. */
  4337. Ext.define('Ext.ux.overrides.rating.Picker', {
  4338. override: 'Ext.ux.rating.Picker',
  4339. //<debug>
  4340. initConfig: function(config) {
  4341. if (config && config.tooltip) {
  4342. config.tip = config.tooltip;
  4343. Ext.log.warn('[Ext.ux.rating.Picker] The "tooltip" config was replaced by "tip"');
  4344. }
  4345. this.callParent([
  4346. config
  4347. ]);
  4348. },
  4349. //</debug>
  4350. updateTooltipText: function(text) {
  4351. var innerEl = this.innerEl,
  4352. QuickTips = Ext.tip && Ext.tip.QuickTipManager,
  4353. tip = QuickTips && QuickTips.tip,
  4354. target;
  4355. if (QuickTips) {
  4356. innerEl.dom.setAttribute('data-qtip', text);
  4357. this.trackerEl.dom.setAttribute('data-qtip', text);
  4358. // If the QuickTipManager is active over our widget, we need to update
  4359. // the tooltip text directly.
  4360. target = tip && tip.activeTarget;
  4361. target = target && target.el;
  4362. if (target && innerEl.contains(target)) {
  4363. tip.update(text);
  4364. }
  4365. }
  4366. }
  4367. });
  4368. /**
  4369. * A DragDrop implementation specialized for use with BoxReorderer.
  4370. */
  4371. Ext.define('Ext.ux.dd.BoxContainerDD', {
  4372. extend: 'Ext.dd.DD',
  4373. /**
  4374. * @method alignElWithMouse
  4375. * @member Ext.dd.DD
  4376. * @inheritdoc
  4377. */
  4378. alignElWithMouse: function(el, iPageX, iPageY) {
  4379. var me = this,
  4380. oCoord = me.getTargetCoord(iPageX, iPageY),
  4381. x = oCoord.x,
  4382. y = oCoord.y,
  4383. fly = el.dom ? el : Ext.fly(el, '_dd'),
  4384. aCoord, newLeft, newTop;
  4385. if (!me.deltaSetXY) {
  4386. aCoord = [
  4387. Math.max(0, x),
  4388. Math.max(0, y)
  4389. ];
  4390. fly.setXY(aCoord);
  4391. newLeft = me.getLocalX(fly);
  4392. newTop = fly.getLocalY();
  4393. me.deltaSetXY = [
  4394. newLeft - x,
  4395. newTop - y
  4396. ];
  4397. } else {
  4398. me.setLocalXY(fly, Math.max(0, x + me.deltaSetXY[0]), Math.max(0, y + me.deltaSetXY[1]));
  4399. }
  4400. me.cachePosition(x, y);
  4401. me.autoScroll(x, y, el.offsetHeight, el.offsetWidth);
  4402. return oCoord;
  4403. }
  4404. });
  4405. /**
  4406. * Base class from Ext.ux.TabReorderer.
  4407. */
  4408. Ext.define('Ext.ux.BoxReorderer', {
  4409. extend: 'Ext.plugin.Abstract',
  4410. alias: 'plugin.boxreorderer',
  4411. requires: [
  4412. 'Ext.ux.dd.BoxContainerDD'
  4413. ],
  4414. mixins: {
  4415. observable: 'Ext.util.Observable'
  4416. },
  4417. /**
  4418. * @cfg {String} itemSelector
  4419. * A {@link Ext.DomQuery DomQuery} selector which identifies the encapsulating elements of child
  4420. * Components which participate in reordering.
  4421. */
  4422. itemSelector: '.x-box-item',
  4423. /**
  4424. * @cfg {Mixed} animate
  4425. * If truthy, child reordering is animated so that moved boxes slide smoothly into position.
  4426. * If this option is numeric, it is used as the animation duration in milliseconds.
  4427. */
  4428. animate: 100,
  4429. /**
  4430. * @event StartDrag
  4431. * Fires when dragging of a child Component begins.
  4432. * @param {Ext.ux.BoxReorderer} this
  4433. * @param {Ext.container.Container} container The owning Container
  4434. * @param {Ext.Component} dragCmp The Component being dragged
  4435. * @param {Number} idx The start index of the Component being dragged.
  4436. */
  4437. /**
  4438. * @event Drag
  4439. * Fires during dragging of a child Component.
  4440. * @param {Ext.ux.BoxReorderer} this
  4441. * @param {Ext.container.Container} container The owning Container
  4442. * @param {Ext.Component} dragCmp The Component being dragged
  4443. * @param {Number} startIdx The index position from which the Component was initially dragged.
  4444. * @param {Number} idx The current closest index to which the Component would drop.
  4445. */
  4446. /**
  4447. * @event ChangeIndex
  4448. * Fires when dragging of a child Component causes its drop index to change.
  4449. * @param {Ext.ux.BoxReorderer} this
  4450. * @param {Ext.container.Container} container The owning Container
  4451. * @param {Ext.Component} dragCmp The Component being dragged
  4452. * @param {Number} startIdx The index position from which the Component was initially dragged.
  4453. * @param {Number} idx The current closest index to which the Component would drop.
  4454. */
  4455. /**
  4456. * @event Drop
  4457. * Fires when a child Component is dropped at a new index position.
  4458. * @param {Ext.ux.BoxReorderer} this
  4459. * @param {Ext.container.Container} container The owning Container
  4460. * @param {Ext.Component} dragCmp The Component being dropped
  4461. * @param {Number} startIdx The index position from which the Component was initially dragged.
  4462. * @param {Number} idx The index at which the Component is being dropped.
  4463. */
  4464. constructor: function() {
  4465. this.callParent(arguments);
  4466. this.mixins.observable.constructor.call(this);
  4467. },
  4468. init: function(container) {
  4469. var me = this,
  4470. layout = container.getLayout();
  4471. me.container = container;
  4472. // We must use LTR method names and properties.
  4473. // The underlying Element APIs normalize them.
  4474. me.names = layout._props[layout.type].names;
  4475. // Set our animatePolicy to animate the start position (ie x for HBox, y for VBox)
  4476. me.animatePolicy = {};
  4477. me.animatePolicy[me.names.x] = true;
  4478. // Initialize the DD on first layout, when the innerCt has been created.
  4479. me.container.on({
  4480. scope: me,
  4481. boxready: me.onBoxReady,
  4482. beforedestroy: me.onContainerDestroy
  4483. });
  4484. },
  4485. /**
  4486. * @private
  4487. * Clear up on Container destroy
  4488. */
  4489. onContainerDestroy: function() {
  4490. var dd = this.dd;
  4491. if (dd) {
  4492. dd.unreg();
  4493. this.dd = null;
  4494. }
  4495. },
  4496. onBoxReady: function() {
  4497. var me = this,
  4498. layout = me.container.getLayout(),
  4499. names = me.names,
  4500. dd;
  4501. dd = me.dd = new Ext.ux.dd.BoxContainerDD(layout.innerCt, me.container.id + '-reorderer');
  4502. Ext.apply(dd, {
  4503. animate: me.animate,
  4504. reorderer: me,
  4505. container: me.container,
  4506. getDragCmp: me.getDragCmp,
  4507. clickValidator: Ext.Function.createInterceptor(dd.clickValidator, me.clickValidator, me, false),
  4508. onMouseDown: me.onMouseDown,
  4509. startDrag: me.startDrag,
  4510. onDrag: me.onDrag,
  4511. endDrag: me.endDrag,
  4512. getNewIndex: me.getNewIndex,
  4513. doSwap: me.doSwap,
  4514. findReorderable: me.findReorderable,
  4515. names: names
  4516. });
  4517. // Decide which dimension we are measuring, and which measurement metric defines
  4518. // the *start* of the box depending upon orientation.
  4519. dd.dim = names.width;
  4520. dd.startAttr = names.beforeX;
  4521. dd.endAttr = names.afterX;
  4522. },
  4523. getDragCmp: function(e) {
  4524. return this.container.getChildByElement(e.getTarget(this.itemSelector, 10));
  4525. },
  4526. // check if the clicked component is reorderable
  4527. clickValidator: function(e) {
  4528. var cmp = this.getDragCmp(e);
  4529. // If cmp is null, this expression MUST be coerced to boolean so that
  4530. // createInterceptor is able to test it against false
  4531. return !!(cmp && cmp.reorderable !== false);
  4532. },
  4533. onMouseDown: function(e) {
  4534. var me = this,
  4535. container = me.container,
  4536. containerBox, cmpEl, cmpBox;
  4537. // Ascertain which child Component is being mousedowned
  4538. me.dragCmp = me.getDragCmp(e);
  4539. if (me.dragCmp) {
  4540. cmpEl = me.dragCmp.getEl();
  4541. me.startIndex = me.curIndex = container.items.indexOf(me.dragCmp);
  4542. // Start position of dragged Component
  4543. cmpBox = cmpEl.getBox();
  4544. // Last tracked start position
  4545. me.lastPos = cmpBox[me.startAttr];
  4546. // Calculate constraints depending upon orientation
  4547. // Calculate offset from mouse to dragEl position
  4548. containerBox = container.el.getBox();
  4549. if (me.dim === 'width') {
  4550. me.minX = containerBox.left;
  4551. me.maxX = containerBox.right - cmpBox.width;
  4552. me.minY = me.maxY = cmpBox.top;
  4553. me.deltaX = e.getX() - cmpBox.left;
  4554. } else {
  4555. me.minY = containerBox.top;
  4556. me.maxY = containerBox.bottom - cmpBox.height;
  4557. me.minX = me.maxX = cmpBox.left;
  4558. me.deltaY = e.getY() - cmpBox.top;
  4559. }
  4560. me.constrainY = me.constrainX = true;
  4561. }
  4562. },
  4563. startDrag: function() {
  4564. var me = this,
  4565. dragCmp = me.dragCmp,
  4566. targetEl, dom, left, top, scrollable;
  4567. if (dragCmp) {
  4568. // For the entire duration of dragging the *Element*, defeat any positioning
  4569. // and animation of the dragged *Component*
  4570. scrollable = me.container.getScrollable();
  4571. if (scrollable) {
  4572. // TODO remove this workaround
  4573. scrollable.scrollBy(-1).then(function() {
  4574. scrollable.scrollBy(1);
  4575. });
  4576. }
  4577. dragCmp.setPosition = Ext.emptyFn;
  4578. dragCmp.animate = false;
  4579. // Animate the BoxLayout just for the duration of the drag operation.
  4580. if (me.animate) {
  4581. me.container.getLayout().animatePolicy = me.reorderer.animatePolicy;
  4582. }
  4583. // We drag the Component element
  4584. me.dragElId = dragCmp.getEl().id;
  4585. me.reorderer.fireEvent('StartDrag', me, me.container, dragCmp, me.curIndex);
  4586. // Suspend events, and set the disabled flag so that the mousedown and mouseup events
  4587. // that are going to take place do not cause any other UI interaction.
  4588. dragCmp.suspendEvents();
  4589. dragCmp.disabled = true;
  4590. dragCmp.el.setStyle('zIndex', 100);
  4591. // add a spacer to the tab container so it doesn't shrink while we're dragging a tab
  4592. if (!dragCmp.nextSibling()) {
  4593. targetEl = me.container.layout.targetEl;
  4594. dom = targetEl.dom;
  4595. left = dom.scrollWidth - 1;
  4596. top = dom.scrollHeight - 1;
  4597. me.spacerEl = Ext.dom.Helper.append(targetEl, {
  4598. tag: 'div',
  4599. style: 'width: 1px;' + 'height: 1px;' + 'position: absolute;' + 'left: ' + left + 'px;' + 'top: ' + top + 'px;"'
  4600. });
  4601. }
  4602. } else {
  4603. me.dragElId = null;
  4604. }
  4605. },
  4606. /**
  4607. * @private
  4608. * Find next or previous reorderable component index.
  4609. * @param {Number} newIndex The initial drop index.
  4610. * @return {Number} The index of the reorderable component.
  4611. */
  4612. findReorderable: function(newIndex) {
  4613. var me = this,
  4614. items = me.container.items,
  4615. newItem;
  4616. if (items.getAt(newIndex).reorderable === false) {
  4617. newItem = items.getAt(newIndex);
  4618. if (newIndex > me.startIndex) {
  4619. while (newItem && newItem.reorderable === false) {
  4620. newIndex++;
  4621. newItem = items.getAt(newIndex);
  4622. }
  4623. } else {
  4624. while (newItem && newItem.reorderable === false) {
  4625. newIndex--;
  4626. newItem = items.getAt(newIndex);
  4627. }
  4628. }
  4629. }
  4630. newIndex = Math.min(Math.max(newIndex, 0), items.getCount() - 1);
  4631. if (items.getAt(newIndex).reorderable === false) {
  4632. return -1;
  4633. }
  4634. return newIndex;
  4635. },
  4636. /**
  4637. * @private
  4638. * Swap 2 components.
  4639. * @param {Number} newIndex The initial drop index.
  4640. */
  4641. doSwap: function(newIndex) {
  4642. var me = this,
  4643. items = me.container.items,
  4644. container = me.container,
  4645. orig, dest, tmpIndex;
  4646. newIndex = me.findReorderable(newIndex);
  4647. if (newIndex === -1 || newIndex === me.curIndex) {
  4648. return;
  4649. }
  4650. me.reorderer.fireEvent('ChangeIndex', me, container, me.dragCmp, me.startIndex, newIndex);
  4651. orig = items.getAt(me.curIndex);
  4652. dest = items.getAt(newIndex);
  4653. items.remove(orig);
  4654. tmpIndex = Math.min(Math.max(newIndex, 0), items.getCount() - 1);
  4655. items.insert(tmpIndex, orig);
  4656. items.remove(dest);
  4657. items.insert(me.curIndex, dest);
  4658. // Make the Box Container the topmost layout participant during the layout.
  4659. container.updateLayout({
  4660. isRoot: true
  4661. });
  4662. me.curIndex = newIndex;
  4663. },
  4664. onDrag: function(e) {
  4665. var me = this,
  4666. newIndex;
  4667. newIndex = me.getNewIndex(e.getPoint());
  4668. if ((newIndex !== undefined)) {
  4669. me.reorderer.fireEvent('Drag', me, me.container, me.dragCmp, me.startIndex, me.curIndex);
  4670. me.doSwap(newIndex);
  4671. }
  4672. },
  4673. endDrag: function(e) {
  4674. var me = this,
  4675. dragCmp = me.dragCmp,
  4676. container = me.container,
  4677. layout = container.getLayout(),
  4678. temp;
  4679. if (e) {
  4680. e.stopEvent();
  4681. }
  4682. if (dragCmp) {
  4683. delete me.dragElId;
  4684. // Reinstate the Component's positioning method after mouseup,
  4685. // and allow the layout system to animate it.
  4686. delete dragCmp.setPosition;
  4687. dragCmp.animate = true;
  4688. // Ensure the lastBox is correct for the animation system to restore
  4689. // to when it creates the "from" animation frame
  4690. dragCmp.lastBox[me.names.x] = dragCmp.getPosition(true)[me.names.widthIndex];
  4691. // Make the Box Container the topmost layout participant during the layout.
  4692. container.updateLayout({
  4693. isRoot: true
  4694. });
  4695. // Attempt to hook into the afteranimate event of the drag Component to call the cleanup
  4696. temp = Ext.fx.Manager.getFxQueue(dragCmp.el.id)[0];
  4697. if (temp) {
  4698. temp.on({
  4699. afteranimate: me.reorderer.afterBoxReflow,
  4700. scope: me
  4701. });
  4702. } else // If not animated, clean up after the mouseup has happened so that
  4703. // we don't click the thing being dragged
  4704. {
  4705. Ext.asap(me.reorderer.afterBoxReflow, me);
  4706. }
  4707. if (me.animate) {
  4708. delete layout.animatePolicy;
  4709. }
  4710. me.reorderer.fireEvent('drop', me, container, dragCmp, me.startIndex, me.curIndex);
  4711. }
  4712. },
  4713. /**
  4714. * @private
  4715. * Called after the boxes have been reflowed after the drop.
  4716. * Re-enabled the dragged Component.
  4717. */
  4718. afterBoxReflow: function() {
  4719. var me = this,
  4720. spacerEl = Ext.fly(me.spacerEl),
  4721. dragCmp = me.dragCmp;
  4722. dragCmp.el.setStyle('zIndex', '');
  4723. dragCmp.disabled = false;
  4724. dragCmp.resumeEvents();
  4725. // remove the spacer that was added when the drag was started
  4726. if (spacerEl) {
  4727. spacerEl.remove();
  4728. me.spacerEl = null;
  4729. }
  4730. },
  4731. /**
  4732. * @private
  4733. * Calculate drop index based upon the dragEl's position.
  4734. */
  4735. getNewIndex: function(pointerPos) {
  4736. var me = this,
  4737. dragEl = me.getDragEl(),
  4738. dragBox = Ext.fly(dragEl).getBox(),
  4739. targetEl, targetBox, targetMidpoint,
  4740. i = 0,
  4741. it = me.container.items.items,
  4742. ln = it.length,
  4743. lastPos = me.lastPos;
  4744. me.lastPos = dragBox[me.startAttr];
  4745. for (; i < ln; i++) {
  4746. targetEl = it[i].getEl();
  4747. // Only look for a drop point if this found item is an item according to our selector
  4748. if (targetEl.dom !== dragEl && targetEl.is(me.reorderer.itemSelector)) {
  4749. targetBox = targetEl.getBox();
  4750. targetMidpoint = targetBox[me.startAttr] + (targetBox[me.dim] >> 1);
  4751. if (i < me.curIndex) {
  4752. if ((dragBox[me.startAttr] < lastPos) && (dragBox[me.startAttr] < (targetMidpoint - 5))) {
  4753. return i;
  4754. }
  4755. } else if (i > me.curIndex) {
  4756. if ((dragBox[me.startAttr] > lastPos) && (dragBox[me.endAttr] > (targetMidpoint + 5))) {
  4757. return i;
  4758. }
  4759. }
  4760. }
  4761. }
  4762. }
  4763. });
  4764. /**
  4765. * This plugin can enable a cell to cell drag and drop operation within the same grid view.
  4766. *
  4767. * Note that the plugin must be added to the grid view, not to the grid panel. For example,
  4768. * using {@link Ext.panel.Table viewConfig}:
  4769. *
  4770. * viewConfig: {
  4771. * plugins: {
  4772. * celldragdrop: {
  4773. * // Remove text from source cell and replace with value of emptyText.
  4774. * applyEmptyText: true,
  4775. *
  4776. * //emptyText: Ext.String.htmlEncode('<<foo>>'),
  4777. *
  4778. * // Will only allow drops of the same type.
  4779. * enforceType: true
  4780. * }
  4781. * }
  4782. * }
  4783. */
  4784. Ext.define('Ext.ux.CellDragDrop', {
  4785. extend: 'Ext.plugin.Abstract',
  4786. alias: 'plugin.celldragdrop',
  4787. uses: [
  4788. 'Ext.view.DragZone'
  4789. ],
  4790. /**
  4791. * @cfg {Boolean} enforceType
  4792. * Set to `true` to only allow drops of the same type.
  4793. *
  4794. * Defaults to `false`.
  4795. */
  4796. enforceType: false,
  4797. /**
  4798. * @cfg {Boolean} applyEmptyText
  4799. * If `true`, then use the value of {@link #emptyText} to replace the drag record's value
  4800. * after a node drop. Note that, if dropped on a cell of a different type, it will convert
  4801. * the default text according to its own conversion rules.
  4802. *
  4803. * Defaults to `false`.
  4804. */
  4805. applyEmptyText: false,
  4806. /**
  4807. * @cfg {String} emptyText
  4808. * If {@link #applyEmptyText} is `true`, then this value as the drag record's value after
  4809. * a node drop.
  4810. *
  4811. * Defaults to an empty string.
  4812. */
  4813. emptyText: '',
  4814. /**
  4815. * @cfg {String} dropBackgroundColor
  4816. * The default background color for when a drop is allowed.
  4817. *
  4818. * Defaults to green.
  4819. */
  4820. dropBackgroundColor: 'green',
  4821. /**
  4822. * @cfg {String} noDropBackgroundColor
  4823. * The default background color for when a drop is not allowed.
  4824. *
  4825. * Defaults to red.
  4826. */
  4827. noDropBackgroundColor: 'red',
  4828. /**
  4829. * @cfg {String} dragText
  4830. * The text to show while dragging.
  4831. *
  4832. * Two placeholders can be used in the text:
  4833. *
  4834. * - `{0}` The number of selected items.
  4835. * - `{1}` 's' when more than 1 items (only useful for English).
  4836. * @locale
  4837. */
  4838. dragText: '{0} selected row{1}',
  4839. /**
  4840. * @cfg {String} ddGroup
  4841. * A named drag drop group to which this object belongs. If a group is specified, then both
  4842. * the DragZones and DropZone used by this plugin will only interact with other drag drop
  4843. * objects in the same group.
  4844. */
  4845. ddGroup: "GridDD",
  4846. /**
  4847. * @cfg {Boolean} enableDrop
  4848. * Set to `false` to disallow the View from accepting drop gestures.
  4849. */
  4850. enableDrop: true,
  4851. /**
  4852. * @cfg {Boolean} enableDrag
  4853. * Set to `false` to disallow dragging items from the View.
  4854. */
  4855. enableDrag: true,
  4856. /**
  4857. * @cfg {Object/Boolean} containerScroll
  4858. * True to register this container with the Scrollmanager for auto scrolling during drag
  4859. * operations. A {@link Ext.dd.ScrollManager} configuration may also be passed.
  4860. */
  4861. containerScroll: false,
  4862. init: function(view) {
  4863. var me = this;
  4864. view.on('render', me.onViewRender, me, {
  4865. single: true
  4866. });
  4867. },
  4868. destroy: function() {
  4869. var me = this;
  4870. me.dragZone = me.dropZone = Ext.destroy(me.dragZone, me.dropZone);
  4871. me.callParent();
  4872. },
  4873. enable: function() {
  4874. var me = this;
  4875. if (me.dragZone) {
  4876. me.dragZone.unlock();
  4877. }
  4878. if (me.dropZone) {
  4879. me.dropZone.unlock();
  4880. }
  4881. me.callParent();
  4882. },
  4883. disable: function() {
  4884. var me = this;
  4885. if (me.dragZone) {
  4886. me.dragZone.lock();
  4887. }
  4888. if (me.dropZone) {
  4889. me.dropZone.lock();
  4890. }
  4891. me.callParent();
  4892. },
  4893. onViewRender: function(view) {
  4894. var me = this,
  4895. scrollEl;
  4896. if (me.enableDrag) {
  4897. if (me.containerScroll) {
  4898. scrollEl = view.getEl();
  4899. }
  4900. me.dragZone = new Ext.view.DragZone({
  4901. view: view,
  4902. ddGroup: me.dragGroup || me.ddGroup,
  4903. dragText: me.dragText,
  4904. containerScroll: me.containerScroll,
  4905. scrollEl: scrollEl,
  4906. getDragData: function(e) {
  4907. var view = this.view,
  4908. item = e.getTarget(view.getItemSelector()),
  4909. record = view.getRecord(item),
  4910. cell = e.getTarget(view.getCellSelector()),
  4911. dragEl, header;
  4912. if (item) {
  4913. dragEl = document.createElement('div');
  4914. dragEl.className = 'x-form-text';
  4915. dragEl.appendChild(document.createTextNode(cell.textContent || cell.innerText));
  4916. header = view.getHeaderByCell(cell);
  4917. return {
  4918. event: new Ext.EventObjectImpl(e),
  4919. ddel: dragEl,
  4920. item: e.target,
  4921. columnName: header.dataIndex,
  4922. record: record
  4923. };
  4924. }
  4925. },
  4926. onInitDrag: function(x, y) {
  4927. var self = this,
  4928. data = self.dragData,
  4929. view = self.view,
  4930. selectionModel = view.getSelectionModel(),
  4931. record = data.record,
  4932. el = data.ddel;
  4933. // Update the selection to match what would have been selected if the user had
  4934. // done a full click on the target node rather than starting a drag from it.
  4935. if (!selectionModel.isSelected(record)) {
  4936. selectionModel.select(record, true);
  4937. }
  4938. Ext.fly(self.ddel).update(el.textContent || el.innerText);
  4939. self.proxy.update(self.ddel);
  4940. self.onStartDrag(x, y);
  4941. return true;
  4942. }
  4943. });
  4944. }
  4945. if (me.enableDrop) {
  4946. me.dropZone = new Ext.dd.DropZone(view.el, {
  4947. view: view,
  4948. ddGroup: me.dropGroup || me.ddGroup,
  4949. containerScroll: true,
  4950. getTargetFromEvent: function(e) {
  4951. var self = this,
  4952. view = self.view,
  4953. cell = e.getTarget(view.cellSelector),
  4954. row, header;
  4955. // Ascertain whether the mousemove is within a grid cell.
  4956. if (cell) {
  4957. row = view.findItemByChild(cell);
  4958. header = view.getHeaderByCell(cell);
  4959. if (row && header) {
  4960. return {
  4961. node: cell,
  4962. record: view.getRecord(row),
  4963. columnName: header.dataIndex
  4964. };
  4965. }
  4966. }
  4967. },
  4968. // On Node enter, see if it is valid for us to drop the field on that type of column
  4969. onNodeEnter: function(target, dd, e, dragData) {
  4970. var self = this,
  4971. destType, sourceType;
  4972. destType = target.record.getField(target.columnName).type.toUpperCase();
  4973. sourceType = dragData.record.getField(dragData.columnName).type.toUpperCase();
  4974. delete self.dropOK;
  4975. // Return if no target node or if over the same cell as the source of the drag.
  4976. if (!target || target.node === dragData.item.parentNode) {
  4977. return;
  4978. }
  4979. // Check whether the data type of the column being dropped on accepts the
  4980. // dragged field type. If so, set dropOK flag, and highlight the target node.
  4981. if (me.enforceType && destType !== sourceType) {
  4982. self.dropOK = false;
  4983. if (me.noDropCls) {
  4984. Ext.fly(target.node).addCls(me.noDropCls);
  4985. } else {
  4986. Ext.fly(target.node).applyStyles({
  4987. backgroundColor: me.noDropBackgroundColor
  4988. });
  4989. }
  4990. return false;
  4991. }
  4992. self.dropOK = true;
  4993. if (me.dropCls) {
  4994. Ext.fly(target.node).addCls(me.dropCls);
  4995. } else {
  4996. Ext.fly(target.node).applyStyles({
  4997. backgroundColor: me.dropBackgroundColor
  4998. });
  4999. }
  5000. },
  5001. // Return the class name to add to the drag proxy. This provides a visual indication
  5002. // of drop allowed or not allowed.
  5003. onNodeOver: function(target, dd, e, dragData) {
  5004. return this.dropOK ? this.dropAllowed : this.dropNotAllowed;
  5005. },
  5006. // Highlight the target node.
  5007. onNodeOut: function(target, dd, e, dragData) {
  5008. var cls = this.dropOK ? me.dropCls : me.noDropCls;
  5009. if (cls) {
  5010. Ext.fly(target.node).removeCls(cls);
  5011. } else {
  5012. Ext.fly(target.node).applyStyles({
  5013. backgroundColor: ''
  5014. });
  5015. }
  5016. },
  5017. // Process the drop event if we have previously ascertained that a drop is OK.
  5018. onNodeDrop: function(target, dd, e, dragData) {
  5019. if (this.dropOK) {
  5020. target.record.set(target.columnName, dragData.record.get(dragData.columnName));
  5021. if (me.applyEmptyText) {
  5022. dragData.record.set(dragData.columnName, me.emptyText);
  5023. }
  5024. return true;
  5025. }
  5026. },
  5027. onCellDrop: Ext.emptyFn
  5028. });
  5029. }
  5030. }
  5031. });
  5032. /**
  5033. * @class Ext.ux.DataTip
  5034. * @extends Ext.tip.ToolTip
  5035. * This plugin implements automatic tooltip generation for an arbitrary number of child nodes
  5036. * *within* a Component.
  5037. *
  5038. * This plugin is applied to a high level Component, which contains repeating elements,
  5039. * and depending on the host Component type, it automatically selects
  5040. * a {@link Ext.ToolTip#delegate delegate} so that it appears when the mouse enters a sub-element.
  5041. *
  5042. * When applied to a GridPanel, this ToolTip appears when over a row, and the Record's data
  5043. * is applied using this object's {@link #tpl} template.
  5044. *
  5045. * When applied to a DataView, this ToolTip appears when over a view node, and the Record's data
  5046. * is applied using this object's {@link #tpl} template.
  5047. *
  5048. * When applied to a TreePanel, this ToolTip appears when over a tree node, and the Node's
  5049. * {@link Ext.data.Model} record data is applied using this object's {@link #tpl} template.
  5050. *
  5051. * When applied to a FormPanel, this ToolTip appears when over a Field, and the Field's `tooltip`
  5052. * property is used is applied using this object's {@link #tpl} template, or if it is a string,
  5053. * used as HTML content. If there is no `tooltip` property, the field itself is used
  5054. * as the template's data object.
  5055. *
  5056. * If more complex logic is needed to determine content, then the {@link #beforeshow} event
  5057. * may be used. This class also publishes a **`beforeshowtip`** event through its host Component.
  5058. * The *host Component* fires the **`beforeshowtip`** event.
  5059. */
  5060. Ext.define('Ext.ux.DataTip', function(DataTip) {
  5061. // Target the body (if the host is a Panel), or, if there is no body, the main Element.
  5062. function onHostRender() {
  5063. var e = this.isXType('panel') ? this.body : this.el;
  5064. if (this.dataTip.renderToTarget) {
  5065. this.dataTip.render(e);
  5066. }
  5067. this.dataTip.setTarget(e);
  5068. }
  5069. function updateTip(tip, data) {
  5070. if (tip.rendered) {
  5071. if (tip.host.fireEvent('beforeshowtip', tip.eventHost, tip, data) === false) {
  5072. return false;
  5073. }
  5074. tip.update(data);
  5075. } else {
  5076. if (Ext.isString(data)) {
  5077. tip.html = data;
  5078. } else {
  5079. tip.data = data;
  5080. }
  5081. }
  5082. }
  5083. function beforeViewTipShow(tip) {
  5084. var rec = this.view.getRecord(tip.triggerElement),
  5085. data;
  5086. if (rec) {
  5087. data = tip.initialConfig.data ? Ext.apply(tip.initialConfig.data, rec.data) : rec.data;
  5088. return updateTip(tip, data);
  5089. } else {
  5090. return false;
  5091. }
  5092. }
  5093. function beforeFormTipShow(tip) {
  5094. var field = Ext.getCmp(tip.triggerElement.id);
  5095. if (field && (field.tooltip || tip.tpl)) {
  5096. return updateTip(tip, field.tooltip || field);
  5097. } else {
  5098. return false;
  5099. }
  5100. }
  5101. return {
  5102. extend: 'Ext.tip.ToolTip',
  5103. mixins: {
  5104. plugin: 'Ext.plugin.Abstract'
  5105. },
  5106. alias: 'plugin.datatip',
  5107. lockableScope: 'both',
  5108. constructor: function(config) {
  5109. var me = this;
  5110. me.callParent([
  5111. config
  5112. ]);
  5113. me.mixins.plugin.constructor.call(me, config);
  5114. },
  5115. init: function(host) {
  5116. var me = this;
  5117. me.mixins.plugin.init.call(me, host);
  5118. host.dataTip = me;
  5119. me.host = host;
  5120. if (host.isXType('tablepanel')) {
  5121. me.view = host.getView();
  5122. if (host.ownerLockable) {
  5123. me.host = host.ownerLockable;
  5124. }
  5125. me.delegate = me.delegate || me.view.rowSelector;
  5126. me.on('beforeshow', beforeViewTipShow);
  5127. } else if (host.isXType('dataview')) {
  5128. me.view = me.host;
  5129. me.delegate = me.delegate || host.itemSelector;
  5130. me.on('beforeshow', beforeViewTipShow);
  5131. } else if (host.isXType('form')) {
  5132. me.delegate = '.' + Ext.form.Labelable.prototype.formItemCls;
  5133. me.on('beforeshow', beforeFormTipShow);
  5134. } else if (host.isXType('combobox')) {
  5135. me.view = host.getPicker();
  5136. me.delegate = me.delegate || me.view.getItemSelector();
  5137. me.on('beforeshow', beforeViewTipShow);
  5138. }
  5139. if (host.rendered) {
  5140. onHostRender.call(host);
  5141. } else {
  5142. host.onRender = Ext.Function.createSequence(host.onRender, onHostRender);
  5143. }
  5144. }
  5145. };
  5146. });
  5147. /**
  5148. * Transition plugin for DataViews
  5149. */
  5150. Ext.define('Ext.ux.DataView.Animated', {
  5151. alias: 'plugin.ux-animated-dataview',
  5152. /**
  5153. * @property defaults
  5154. * @type Object
  5155. * Default configuration options for all DataViewTransition instances
  5156. */
  5157. defaults: {
  5158. duration: 750,
  5159. idProperty: 'id'
  5160. },
  5161. /**
  5162. * Creates the plugin instance, applies defaults
  5163. * @constructor
  5164. * @param {Object} config Optional config object
  5165. */
  5166. constructor: function(config) {
  5167. Ext.apply(this, config || {}, this.defaults);
  5168. },
  5169. /**
  5170. * Initializes the transition plugin. Overrides the dataview's default refresh function
  5171. * @param {Ext.view.View} dataview The dataview
  5172. */
  5173. init: function(dataview) {
  5174. var me = this,
  5175. store = dataview.store,
  5176. items = dataview.all,
  5177. task = {
  5178. interval: 20
  5179. },
  5180. duration = me.duration;
  5181. /**
  5182. * @property dataview
  5183. * @type Ext.view.View
  5184. * Reference to the DataView this instance is bound to
  5185. */
  5186. me.dataview = dataview;
  5187. dataview.blockRefresh = true;
  5188. dataview.updateIndexes = Ext.Function.createSequence(dataview.updateIndexes, function() {
  5189. this.getTargetEl().select(this.itemSelector).each(function(element, composite, index) {
  5190. element.dom.id = Ext.util.Format.format("{0}-{1}", dataview.id, store.getAt(index).internalId);
  5191. }, this);
  5192. }, dataview);
  5193. /**
  5194. * @property dataviewID
  5195. * @type String
  5196. * The string ID of the DataView component. This is used internally when animating
  5197. * child objects
  5198. */
  5199. me.dataviewID = dataview.id;
  5200. /**
  5201. * @property cachedStoreData
  5202. * @type Object
  5203. * A cache of existing store data, keyed by id. This is used to determine
  5204. * whether any items were added or removed from the store on data change
  5205. */
  5206. me.cachedStoreData = {};
  5207. // catch the store data with the snapshot immediately
  5208. me.cacheStoreData(store.data || store.snapshot);
  5209. dataview.on('resize', function() {
  5210. var store = dataview.store;
  5211. if (store.getCount() > 0) {}
  5212. }, // reDraw.call(this, store);
  5213. this);
  5214. // Buffer listenher so that rapid calls, for example a filter followed by a sort
  5215. // Only produce one redraw.
  5216. dataview.store.on({
  5217. datachanged: reDraw,
  5218. scope: this,
  5219. buffer: 50
  5220. });
  5221. function reDraw() {
  5222. var parentEl = dataview.getTargetEl(),
  5223. parentElY = parentEl.getY(),
  5224. parentElPaddingTop = parentEl.getPadding('t'),
  5225. added = me.getAdded(store),
  5226. removed = me.getRemoved(store),
  5227. remaining = me.getRemaining(store),
  5228. itemArray, i, id,
  5229. itemFly = new Ext.dom.Fly(),
  5230. rtl = me.dataview.getInherited().rtl,
  5231. oldPos, newPos,
  5232. styleSide = rtl ? 'right' : 'left',
  5233. newStyle = {},
  5234. oldPositions, newPositions, doAnimate;
  5235. // Not yet rendered
  5236. if (!parentEl) {
  5237. return;
  5238. }
  5239. // Collect nodes that will be removed in the forthcoming refresh so
  5240. // that we can put them back in order to fade them out
  5241. Ext.iterate(removed, function(recId, item) {
  5242. id = me.dataviewID + '-' + recId;
  5243. // Stop any animations for removed items and ensure th.
  5244. Ext.fx.Manager.stopAnimation(id);
  5245. item.dom = Ext.getDom(id);
  5246. if (!item.dom) {
  5247. delete removed[recId];
  5248. }
  5249. });
  5250. me.cacheStoreData(store);
  5251. // stores the current top and left values for each element (discovered below)
  5252. oldPositions = {};
  5253. newPositions = {};
  5254. // Find current positions of elements which are to remain after the refresh.
  5255. Ext.iterate(remaining, function(id, item) {
  5256. if (itemFly.attach(Ext.getDom(me.dataviewID + '-' + id))) {
  5257. oldPos = oldPositions[id] = {
  5258. top: itemFly.getY() - parentElY - itemFly.getMargin('t') - parentElPaddingTop
  5259. };
  5260. oldPos[styleSide] = me.getItemX(itemFly);
  5261. } else {
  5262. delete remaining[id];
  5263. }
  5264. });
  5265. // The view MUST refresh, creating items in the natural flow, and collecting the items
  5266. // so that its item collection is consistent.
  5267. dataview.refresh();
  5268. // Replace removed nodes so that they can be faded out, THEN removed
  5269. Ext.iterate(removed, function(id, item) {
  5270. parentEl.dom.appendChild(item.dom);
  5271. itemFly.attach(item.dom).animate({
  5272. duration: duration,
  5273. opacity: 0,
  5274. callback: function(anim) {
  5275. var el = Ext.get(anim.target.id);
  5276. if (el) {
  5277. el.destroy();
  5278. }
  5279. }
  5280. });
  5281. delete item.dom;
  5282. });
  5283. // We have taken care of any removals.
  5284. // If the store is empty, we are done.
  5285. if (!store.getCount()) {
  5286. return;
  5287. }
  5288. // Collect the correct new positions after the refresh
  5289. itemArray = items.slice();
  5290. // Reverse order so that moving to absolute position does not affect the position of
  5291. // the next one we're looking at.
  5292. for (i = itemArray.length - 1; i >= 0; i--) {
  5293. id = store.getAt(i).internalId;
  5294. itemFly.attach(itemArray[i]);
  5295. newPositions[id] = {
  5296. dom: itemFly.dom,
  5297. top: itemFly.getY() - parentElY - itemFly.getMargin('t') - parentElPaddingTop
  5298. };
  5299. newPositions[id][styleSide] = me.getItemX(itemFly);
  5300. // We're going to absolutely position each item.
  5301. // If it is a "remaining" one from last refesh, shunt it back to
  5302. // its old position from where it will be animated.
  5303. newPos = oldPositions[id] || newPositions[id];
  5304. // set absolute positioning on all DataView items. We need to set position, left and
  5305. // top at the same time to avoid any flickering
  5306. newStyle.position = 'absolute';
  5307. newStyle.top = newPos.top + "px";
  5308. newStyle[styleSide] = newPos.left + "px";
  5309. itemFly.applyStyles(newStyle);
  5310. }
  5311. // This is the function which moves remaining items to their new position
  5312. doAnimate = function() {
  5313. var elapsed = new Date() - task.taskStartTime,
  5314. fraction = elapsed / duration,
  5315. oldPos, newPos, oldTop, newTop, oldLeft, newLeft, diffTop, diffLeft, midTop, midLeft;
  5316. if (fraction >= 1) {
  5317. // At end, return all items to natural flow.
  5318. newStyle.position = newStyle.top = newStyle[styleSide] = '';
  5319. for (id in newPositions) {
  5320. itemFly.attach(newPositions[id].dom).applyStyles(newStyle);
  5321. }
  5322. Ext.TaskManager.stop(task);
  5323. } else {
  5324. // In frame, move each "remaining" item according to time elapsed
  5325. for (id in remaining) {
  5326. oldPos = oldPositions[id];
  5327. newPos = newPositions[id];
  5328. oldTop = oldPos.top;
  5329. newTop = newPos.top;
  5330. oldLeft = oldPos[styleSide];
  5331. newLeft = newPos[styleSide];
  5332. diffTop = fraction * Math.abs(oldTop - newTop);
  5333. diffLeft = fraction * Math.abs(oldLeft - newLeft);
  5334. midTop = oldTop > newTop ? oldTop - diffTop : oldTop + diffTop;
  5335. midLeft = oldLeft > newLeft ? oldLeft - diffLeft : oldLeft + diffLeft;
  5336. newStyle.top = midTop + "px";
  5337. newStyle[styleSide] = midLeft + "px";
  5338. itemFly.attach(newPos.dom).applyStyles(newStyle);
  5339. }
  5340. }
  5341. };
  5342. // Fade in new items
  5343. Ext.iterate(added, function(id, item) {
  5344. if (itemFly.attach(Ext.getDom(me.dataviewID + '-' + id))) {
  5345. itemFly.setOpacity(0);
  5346. itemFly.animate({
  5347. duration: duration,
  5348. opacity: 1
  5349. });
  5350. }
  5351. });
  5352. // Stop any previous animations
  5353. Ext.TaskManager.stop(task);
  5354. task.run = doAnimate;
  5355. Ext.TaskManager.start(task);
  5356. me.cacheStoreData(store);
  5357. }
  5358. },
  5359. getItemX: function(el) {
  5360. var rtl = this.dataview.getInherited().rtl,
  5361. parentEl = el.up('');
  5362. if (rtl) {
  5363. return parentEl.getViewRegion().right - el.getRegion().right + el.getMargin('r');
  5364. } else {
  5365. return el.getX() - parentEl.getX() - el.getMargin('l') - parentEl.getPadding('l');
  5366. }
  5367. },
  5368. /**
  5369. * Caches the records from a store locally for comparison later
  5370. * @param {Ext.data.Store} store The store to cache data from
  5371. */
  5372. cacheStoreData: function(store) {
  5373. var cachedStoreData = this.cachedStoreData = {};
  5374. store.each(function(record) {
  5375. cachedStoreData[record.internalId] = record;
  5376. });
  5377. },
  5378. /**
  5379. * Returns all records that were already in the DataView
  5380. * @return {Object} All existing records
  5381. */
  5382. getExisting: function() {
  5383. return this.cachedStoreData;
  5384. },
  5385. /**
  5386. * Returns the total number of items that are currently visible in the DataView
  5387. * @return {Number} The number of existing items
  5388. */
  5389. getExistingCount: function() {
  5390. var count = 0,
  5391. items = this.getExisting(),
  5392. k;
  5393. // eslint-disable-line no-unused-vars
  5394. for (k in items) {
  5395. count++;
  5396. }
  5397. return count;
  5398. },
  5399. /**
  5400. * Returns all records in the given store that were not already present
  5401. * @param {Ext.data.Store} store The updated store instance
  5402. * @return {Object} Object of records not already present in the dataview in format {id: record}
  5403. */
  5404. getAdded: function(store) {
  5405. var cachedStoreData = this.cachedStoreData,
  5406. added = {};
  5407. store.each(function(record) {
  5408. if (cachedStoreData[record.internalId] == null) {
  5409. added[record.internalId] = record;
  5410. }
  5411. });
  5412. return added;
  5413. },
  5414. /**
  5415. * Returns all records that are present in the DataView but not the new store
  5416. * @param {Ext.data.Store} store The updated store instance
  5417. * @return {Array} Array of records that used to be present
  5418. */
  5419. getRemoved: function(store) {
  5420. var cachedStoreData = this.cachedStoreData,
  5421. removed = {},
  5422. id;
  5423. for (id in cachedStoreData) {
  5424. // eslint-disable-next-line brace-style, semi
  5425. if (store.findBy(function(record) {
  5426. return record.internalId === id;
  5427. }) === -1) {
  5428. removed[id] = cachedStoreData[id];
  5429. }
  5430. }
  5431. return removed;
  5432. },
  5433. /**
  5434. * Returns all records that are already present and are still present in the new store
  5435. * @param {Ext.data.Store} store The updated store instance
  5436. * @return {Object} Object of records that are still present from last time in format
  5437. * {id: record}
  5438. */
  5439. getRemaining: function(store) {
  5440. var cachedStoreData = this.cachedStoreData,
  5441. remaining = {};
  5442. store.each(function(record) {
  5443. if (cachedStoreData[record.internalId] != null) {
  5444. remaining[record.internalId] = record;
  5445. }
  5446. });
  5447. return remaining;
  5448. }
  5449. });
  5450. /**
  5451. *
  5452. */
  5453. Ext.define('Ext.ux.DataView.DragSelector', {
  5454. requires: [
  5455. 'Ext.dd.DragTracker',
  5456. 'Ext.util.Region'
  5457. ],
  5458. alias: 'plugin.dataviewdragselector',
  5459. /**
  5460. * Initializes the plugin by setting up the drag tracker
  5461. */
  5462. init: function(dataview) {
  5463. var scroller = dataview.getScrollable();
  5464. // If the client dataview is scrollable, and this is a PointerEvents device
  5465. // we cannot intercept the pointer to inplement dragselect.
  5466. if (scroller && (scroller.getX() || scroller.getY()) && (Ext.supports.PointerEvents || Ext.supports.MSPointerEvents)) {
  5467. //<debug>
  5468. Ext.log.warn('DragSelector not available on PointerEvent devices');
  5469. //</debug>
  5470. return;
  5471. }
  5472. /**
  5473. * @property dataview
  5474. * @type Ext.view.View
  5475. * The DataView bound to this instance
  5476. */
  5477. this.dataview = dataview;
  5478. dataview.mon(dataview, {
  5479. beforecontainerclick: this.cancelClick,
  5480. scope: this,
  5481. render: {
  5482. fn: this.onRender,
  5483. scope: this,
  5484. single: true
  5485. }
  5486. });
  5487. },
  5488. /**
  5489. * @private
  5490. * Called when the attached DataView is rendered. This sets up the DragTracker instance
  5491. * that will be used to created a dragged selection area
  5492. */
  5493. onRender: function() {
  5494. /**
  5495. * @property tracker
  5496. * @type Ext.dd.DragTracker
  5497. * The DragTracker attached to this instance. Note that the 4 on* functions are called
  5498. * in the scope of the DragTracker ('this' refers to the DragTracker inside those
  5499. * functions), so we pass a reference to the DragSelector so that we can call
  5500. * this class's functions.
  5501. */
  5502. this.tracker = Ext.create('Ext.dd.DragTracker', {
  5503. dataview: this.dataview,
  5504. el: this.dataview.el,
  5505. onBeforeStart: this.onBeforeStart,
  5506. onStart: this.onStart.bind(this),
  5507. onDrag: this.onDrag.bind(this),
  5508. onEnd: Ext.Function.createDelayed(this.onEnd, 100, this)
  5509. });
  5510. /**
  5511. * @property dragRegion
  5512. * @type Ext.util.Region
  5513. * Represents the region currently dragged out by the user. This is used to figure out
  5514. * which dataview nodes are in the selected area and to set the size of the Proxy element
  5515. * used to highlight the current drag area
  5516. */
  5517. this.dragRegion = Ext.create('Ext.util.Region');
  5518. },
  5519. /**
  5520. * @private
  5521. * Listener attached to the DragTracker's onBeforeStart event. Returns false if the drag
  5522. * didn't start within the DataView's el
  5523. */
  5524. onBeforeStart: function(e) {
  5525. return e.target === this.dataview.getEl().dom;
  5526. },
  5527. /**
  5528. * @private
  5529. * Listener attached to the DragTracker's onStart event. Cancel's the DataView's containerclick
  5530. * event from firing and sets the start co-ordinates of the Proxy element. Clears any existing
  5531. * DataView selection
  5532. * @param {Ext.event.Event} e The click event
  5533. */
  5534. onStart: function(e) {
  5535. var dataview = this.dataview;
  5536. // Flag which controls whether the cancelClick method vetoes the processing of the
  5537. // DataView's containerclick event.
  5538. // On IE (where else), this needs to remain set for a millisecond after mouseup because
  5539. // even though the mouse has moved, the mouseup will still trigger a click event.
  5540. this.dragging = true;
  5541. // here we reset and show the selection proxy element and cache the regions each item
  5542. // in the dataview take up
  5543. this.fillRegions();
  5544. this.getProxy().show();
  5545. dataview.getSelectionModel().deselectAll();
  5546. },
  5547. /**
  5548. * @private
  5549. * Reusable handler that's used to cancel the container click event when dragging on the
  5550. * dataview. See onStart for details
  5551. */
  5552. cancelClick: function() {
  5553. return !this.dragging;
  5554. },
  5555. /**
  5556. * @private
  5557. * Listener attached to the DragTracker's onDrag event. Figures out how large the drag selection
  5558. * area should be and updates the proxy element's size to match. Then iterates over all of the
  5559. * rendered items and marks them selected if the drag region touches them
  5560. * @param {Ext.event.Event} e The drag event
  5561. */
  5562. onDrag: function(e) {
  5563. var selModel = this.dataview.getSelectionModel(),
  5564. dragRegion = this.dragRegion,
  5565. bodyRegion = this.bodyRegion,
  5566. proxy = this.getProxy(),
  5567. regions = this.regions,
  5568. length = regions.length,
  5569. startXY = this.tracker.startXY,
  5570. currentXY = this.tracker.getXY(),
  5571. minX = Math.min(startXY[0], currentXY[0]),
  5572. minY = Math.min(startXY[1], currentXY[1]),
  5573. width = Math.abs(startXY[0] - currentXY[0]),
  5574. height = Math.abs(startXY[1] - currentXY[1]),
  5575. region, selected, i;
  5576. Ext.apply(dragRegion, {
  5577. top: minY,
  5578. left: minX,
  5579. right: minX + width,
  5580. bottom: minY + height
  5581. });
  5582. dragRegion.constrainTo(bodyRegion);
  5583. proxy.setBox(dragRegion);
  5584. for (i = 0; i < length; i++) {
  5585. region = regions[i];
  5586. selected = dragRegion.intersect(region);
  5587. if (selected) {
  5588. selModel.select(i, true);
  5589. } else {
  5590. selModel.deselect(i);
  5591. }
  5592. }
  5593. },
  5594. /**
  5595. * @method
  5596. * @private
  5597. * Listener attached to the DragTracker's onEnd event. This is a delayed function which
  5598. * executes 1 millisecond after it has been called. This is because the dragging flag must
  5599. * remain active to cancel the containerclick event which the mouseup event will trigger.
  5600. * @param {Ext.event.Event} e The event object
  5601. */
  5602. onEnd: function(e) {
  5603. var dataview = this.dataview,
  5604. selModel = dataview.getSelectionModel();
  5605. // eslint-disable-line no-unused-vars
  5606. this.dragging = false;
  5607. this.getProxy().hide();
  5608. },
  5609. /**
  5610. * @private
  5611. * Creates a Proxy element that will be used to highlight the drag selection region
  5612. * @return {Ext.Element} The Proxy element
  5613. */
  5614. getProxy: function() {
  5615. if (!this.proxy) {
  5616. this.proxy = this.dataview.getEl().createChild({
  5617. tag: 'div',
  5618. cls: 'x-view-selector'
  5619. });
  5620. }
  5621. return this.proxy;
  5622. },
  5623. /**
  5624. * @private
  5625. * Gets the region taken up by each rendered node in the DataView. We use these regions
  5626. * to figure out which nodes to select based on the selector region the user has dragged out
  5627. */
  5628. fillRegions: function() {
  5629. var dataview = this.dataview,
  5630. regions = this.regions = [];
  5631. dataview.all.each(function(node) {
  5632. regions.push(node.getRegion());
  5633. });
  5634. this.bodyRegion = dataview.getEl().getRegion();
  5635. }
  5636. });
  5637. /**
  5638. * ## Basic DataView with Draggable mixin.
  5639. *
  5640. * Ext.Loader.setPath('Ext.ux', '../../../SDK/extjs/examples/ux');
  5641. *
  5642. * Ext.define('My.cool.View', {
  5643. * extend: 'Ext.view.View',
  5644. *
  5645. * mixins: {
  5646. * draggable: 'Ext.ux.DataView.Draggable'
  5647. * },
  5648. *
  5649. * initComponent: function() {
  5650. * this.mixins.draggable.init(this, {
  5651. * ddConfig: {
  5652. * ddGroup: 'someGroup'
  5653. * }
  5654. * });
  5655. *
  5656. * this.callParent(arguments);
  5657. * }
  5658. * });
  5659. *
  5660. * Ext.onReady(function () {
  5661. * Ext.create('Ext.data.Store', {
  5662. * storeId: 'baseball',
  5663. * fields: ['team', 'established'],
  5664. * data: [
  5665. * { team: 'Atlanta Braves', established: '1871' },
  5666. * { team: 'Miami Marlins', established: '1993' },
  5667. * { team: 'New York Mets', established: '1962' },
  5668. * { team: 'Philadelphia Phillies', established: '1883' },
  5669. * { team: 'Washington Nationals', established: '1969' }
  5670. * ]
  5671. * });
  5672. *
  5673. * Ext.create('My.cool.View', {
  5674. * store: Ext.StoreMgr.get('baseball'),
  5675. * tpl: [
  5676. * '<tpl for=".">',
  5677. * '<p class="team">',
  5678. * 'The {team} were founded in {established}.',
  5679. * '</p>',
  5680. * '</tpl>'
  5681. * ],
  5682. * itemSelector: 'p.team',
  5683. * renderTo: Ext.getBody()
  5684. * });
  5685. * });
  5686. */
  5687. Ext.define('Ext.ux.DataView.Draggable', {
  5688. requires: 'Ext.dd.DragZone',
  5689. /**
  5690. * @cfg {String} ghostCls The CSS class added to the outermost element of the created
  5691. * ghost proxy (defaults to 'x-dataview-draggable-ghost')
  5692. */
  5693. ghostCls: 'x-dataview-draggable-ghost',
  5694. /**
  5695. * @cfg {Ext.XTemplate/Array} ghostTpl The template used in the ghost DataView
  5696. */
  5697. ghostTpl: [
  5698. '<tpl for=".">',
  5699. '{title}',
  5700. // eslint-disable-line indent
  5701. '</tpl>'
  5702. ],
  5703. /**
  5704. * @cfg {Object} ddConfig Config object that is applied to the internally created DragZone
  5705. */
  5706. /**
  5707. * @cfg {String} ghostConfig Config object that is used to configure the internally created
  5708. * DataView
  5709. */
  5710. init: function(dataview, config) {
  5711. /**
  5712. * @property dataview
  5713. * @type Ext.view.View
  5714. * The Ext.view.View instance that this DragZone is attached to
  5715. */
  5716. this.dataview = dataview;
  5717. dataview.on('render', this.onRender, this);
  5718. Ext.apply(this, {
  5719. itemSelector: dataview.itemSelector,
  5720. ghostConfig: {}
  5721. }, config || {});
  5722. Ext.applyIf(this.ghostConfig, {
  5723. itemSelector: 'img',
  5724. cls: this.ghostCls,
  5725. tpl: this.ghostTpl
  5726. });
  5727. },
  5728. /**
  5729. * @private
  5730. * Called when the attached DataView is rendered. Sets up the internal DragZone
  5731. */
  5732. onRender: function() {
  5733. var me = this,
  5734. config = Ext.apply({}, me.ddConfig || {}, {
  5735. dvDraggable: me,
  5736. dataview: me.dataview,
  5737. getDragData: me.getDragData,
  5738. getTreeNode: me.getTreeNode,
  5739. afterRepair: me.afterRepair,
  5740. getRepairXY: me.getRepairXY
  5741. });
  5742. /**
  5743. * @property dragZone
  5744. * @type Ext.dd.DragZone
  5745. * The attached DragZone instane
  5746. */
  5747. me.dragZone = Ext.create('Ext.dd.DragZone', me.dataview.getEl(), config);
  5748. // This is for https://www.w3.org/TR/pointerevents/ platforms.
  5749. // On these platforms, the pointerdown event (single touchstart) is reserved for
  5750. // initiating a scroll gesture. Setting the items draggable defeats that and
  5751. // enables the touchstart event to trigger a drag.
  5752. //
  5753. // Two finger dragging will still scroll on these platforms.
  5754. me.dataview.setItemsDraggable(true);
  5755. },
  5756. getDragData: function(e) {
  5757. var draggable = this.dvDraggable,
  5758. dataview = this.dataview,
  5759. selModel = dataview.getSelectionModel(),
  5760. target = e.getTarget(draggable.itemSelector),
  5761. selected, dragData;
  5762. if (target) {
  5763. // preventDefault is needed here to avoid the browser dragging the image
  5764. // instead of dragging the container like it's supposed to
  5765. e.preventDefault();
  5766. if (!dataview.isSelected(target)) {
  5767. selModel.select(dataview.getRecord(target));
  5768. }
  5769. selected = dataview.getSelectedNodes();
  5770. dragData = {
  5771. copy: true,
  5772. nodes: selected,
  5773. records: selModel.getSelection(),
  5774. item: true
  5775. };
  5776. if (selected.length === 1) {
  5777. dragData.single = true;
  5778. dragData.ddel = target;
  5779. } else {
  5780. dragData.multi = true;
  5781. dragData.ddel = draggable.prepareGhost(selModel.getSelection());
  5782. }
  5783. return dragData;
  5784. }
  5785. return false;
  5786. },
  5787. getTreeNode: function() {},
  5788. // console.log('test');
  5789. afterRepair: function() {
  5790. var nodes = this.dragData.nodes,
  5791. length = nodes.length,
  5792. i;
  5793. this.dragging = false;
  5794. // FIXME: Ext.fly does not work here for some reason, only frames the last node
  5795. for (i = 0; i < length; i++) {
  5796. Ext.get(nodes[i]).frame('#8db2e3', 1);
  5797. }
  5798. },
  5799. /**
  5800. * @private
  5801. * Returns the x and y co-ordinates that the dragged item should be animated back to if it
  5802. * was dropped on an invalid drop target. If we're dragging more than one item we don't animate
  5803. * back and just allow afterRepair to frame each dropped item.
  5804. */
  5805. getRepairXY: function(e) {
  5806. var repairEl, repairXY;
  5807. if (this.dragData.multi) {
  5808. return false;
  5809. } else {
  5810. repairEl = Ext.get(this.dragData.ddel);
  5811. repairXY = repairEl.getXY();
  5812. // take the item's margins and padding into account to make the repair animation
  5813. // line up perfectly
  5814. repairXY[0] += repairEl.getPadding('t') + repairEl.getMargin('t');
  5815. repairXY[1] += repairEl.getPadding('l') + repairEl.getMargin('l');
  5816. return repairXY;
  5817. }
  5818. },
  5819. /**
  5820. * Updates the internal ghost DataView by ensuring it is rendered and contains the correct
  5821. * records
  5822. * @param {Array} records The set of records that is currently selected in the parent DataView
  5823. * @return {HTMLElement} The Ghost DataView's encapsulating HTMLElement.
  5824. */
  5825. prepareGhost: function(records) {
  5826. return this.createGhost(records).getEl().dom;
  5827. },
  5828. /**
  5829. * @private
  5830. * Creates the 'ghost' DataView that follows the mouse cursor during the drag operation.
  5831. * This div is usually a lighter-weight representation of just the nodes that are selected
  5832. * in the parent DataView.
  5833. */
  5834. createGhost: function(records) {
  5835. var me = this,
  5836. store;
  5837. if (me.ghost) {
  5838. (store = me.ghost.store).loadRecords(records);
  5839. } else {
  5840. store = Ext.create('Ext.data.Store', {
  5841. model: records[0].self
  5842. });
  5843. store.loadRecords(records);
  5844. me.ghost = Ext.create('Ext.view.View', Ext.apply({
  5845. renderTo: document.createElement('div'),
  5846. store: store
  5847. }, me.ghostConfig));
  5848. me.ghost.container.skipGarbageCollection = me.ghost.el.skipGarbageCollection = true;
  5849. }
  5850. store.clearData();
  5851. return me.ghost;
  5852. },
  5853. destroy: function() {
  5854. var ghost = this.ghost;
  5855. if (ghost) {
  5856. ghost.container.destroy();
  5857. ghost.destroy();
  5858. }
  5859. this.callParent();
  5860. }
  5861. });
  5862. /**
  5863. *
  5864. */
  5865. Ext.define('Ext.ux.DataView.LabelEditor', {
  5866. extend: 'Ext.Editor',
  5867. alias: 'plugin.dataviewlabeleditor',
  5868. alignment: 'tl-tl',
  5869. completeOnEnter: true,
  5870. cancelOnEsc: true,
  5871. shim: false,
  5872. autoSize: {
  5873. width: 'boundEl',
  5874. height: 'field'
  5875. },
  5876. labelSelector: 'x-editable',
  5877. requires: [
  5878. 'Ext.form.field.Text'
  5879. ],
  5880. constructor: function(config) {
  5881. config.field = config.field || Ext.create('Ext.form.field.Text', {
  5882. allowOnlyWhitespace: false,
  5883. selectOnFocus: true
  5884. });
  5885. this.callParent([
  5886. config
  5887. ]);
  5888. },
  5889. init: function(view) {
  5890. this.view = view;
  5891. this.mon(view, 'afterrender', this.bindEvents, this);
  5892. this.on('complete', this.onSave, this);
  5893. },
  5894. // initialize events
  5895. bindEvents: function() {
  5896. this.mon(this.view.getEl(), {
  5897. click: {
  5898. fn: this.onClick,
  5899. scope: this
  5900. }
  5901. });
  5902. },
  5903. // on mousedown show editor
  5904. onClick: function(e, target) {
  5905. var me = this,
  5906. item, record;
  5907. if (Ext.fly(target).hasCls(me.labelSelector) && !me.editing && !e.ctrlKey && !e.shiftKey) {
  5908. e.stopEvent();
  5909. item = me.view.findItemByChild(target);
  5910. record = me.view.store.getAt(me.view.indexOf(item));
  5911. me.startEdit(target, record.data[me.dataIndex]);
  5912. me.activeRecord = record;
  5913. } else if (me.editing) {
  5914. me.field.blur();
  5915. e.preventDefault();
  5916. }
  5917. },
  5918. // update record
  5919. onSave: function(ed, value) {
  5920. this.activeRecord.set(this.dataIndex, value);
  5921. }
  5922. });
  5923. /**
  5924. * @class Ext.ux.DataViewTransition
  5925. * Transition plugin for DataViews
  5926. */
  5927. /* eslint-disable vars-on-top, one-var */
  5928. Ext.ux.DataViewTransition = Ext.extend(Object, {
  5929. /**
  5930. * @property defaults
  5931. * @type Object
  5932. * Default configuration options for all DataViewTransition instances
  5933. */
  5934. defaults: {
  5935. duration: 750,
  5936. idProperty: 'id'
  5937. },
  5938. /**
  5939. * Creates the plugin instance, applies defaults
  5940. * @constructor
  5941. * @param {Object} config Optional config object
  5942. */
  5943. constructor: function(config) {
  5944. Ext.apply(this, config || {}, this.defaults);
  5945. },
  5946. /**
  5947. * Initializes the transition plugin. Overrides the dataview's default refresh function
  5948. * @param {Ext.view.View} dataview The dataview
  5949. */
  5950. init: function(dataview) {
  5951. /**
  5952. * @property dataview
  5953. * @type Ext.view.View
  5954. * Reference to the DataView this instance is bound to
  5955. */
  5956. this.dataview = dataview;
  5957. var idProperty = this.idProperty;
  5958. dataview.blockRefresh = true;
  5959. dataview.updateIndexes = Ext.Function.createSequence(dataview.updateIndexes, function() {
  5960. this.getTargetEl().select(this.itemSelector).each(function(element, composite, index) {
  5961. element.id = element.dom.id = Ext.util.Format.format("{0}-{1}", dataview.id, dataview.store.getAt(index).get(idProperty));
  5962. }, this);
  5963. }, dataview);
  5964. /**
  5965. * @property dataviewID
  5966. * @type String
  5967. * The string ID of the DataView component. This is used internally when animating
  5968. * child objects
  5969. */
  5970. this.dataviewID = dataview.id;
  5971. /**
  5972. * @property cachedStoreData
  5973. * @type Object
  5974. * A cache of existing store data, keyed by id. This is used to determine
  5975. * whether any items were added or removed from the store on data change
  5976. */
  5977. this.cachedStoreData = {};
  5978. // var store = dataview.store;
  5979. // catch the store data with the snapshot immediately
  5980. this.cacheStoreData(dataview.store.snapshot);
  5981. dataview.store.on('datachanged', function(store) {
  5982. var parentEl = dataview.getTargetEl(),
  5983. calcItem = store.getAt(0),
  5984. added = this.getAdded(store),
  5985. removed = this.getRemoved(store),
  5986. previous = this.getRemaining(store);
  5987. // hide old items
  5988. Ext.each(removed, function(item) {
  5989. Ext.fly(this.dataviewID + '-' + item.get(this.idProperty)).animate({
  5990. remove: false,
  5991. duration: duration,
  5992. opacity: 0,
  5993. useDisplay: true
  5994. });
  5995. }, this);
  5996. // store is empty
  5997. if (calcItem == undefined) {
  5998. // eslint-disable-line eqeqeq
  5999. this.cacheStoreData(store);
  6000. return;
  6001. }
  6002. var el = Ext.get(this.dataviewID + "-" + calcItem.get(this.idProperty)),
  6003. // calculate the number of rows and columns we have
  6004. itemWidth = el.getMargin('lr') + el.getWidth(),
  6005. itemHeight = el.getMargin('bt') + el.getHeight(),
  6006. dvWidth = parentEl.getWidth(),
  6007. columns = Math.floor(dvWidth / itemWidth);
  6008. // make sure the correct styles are applied to the parent element
  6009. parentEl.applyStyles({
  6010. display: 'block',
  6011. position: 'relative'
  6012. });
  6013. // stores the current top and left values for each element (discovered below)
  6014. var oldPositions = {},
  6015. newPositions = {},
  6016. elCache = {};
  6017. // find current positions of each element and save a reference in the elCache
  6018. Ext.iterate(previous, function(id, item) {
  6019. // eslint-disable-next-line no-redeclare
  6020. var id = item.get(this.idProperty),
  6021. el = elCache[id] = Ext.get(this.dataviewID + '-' + id);
  6022. oldPositions[id] = {
  6023. top: el.getY() - parentEl.getY() - el.getMargin('t') - parentEl.getPadding('t'),
  6024. left: el.getX() - parentEl.getX() - el.getMargin('l') - parentEl.getPadding('l')
  6025. };
  6026. }, this);
  6027. // set absolute positioning on all DataView items. We need to set position, left and
  6028. // top at the same time to avoid any flickering
  6029. Ext.iterate(previous, function(id, item) {
  6030. var oldPos = oldPositions[id],
  6031. el = elCache[id];
  6032. if (el.getStyle('position') !== 'absolute') {
  6033. elCache[id].applyStyles({
  6034. position: 'absolute',
  6035. left: oldPos.left + "px",
  6036. top: oldPos.top + "px",
  6037. // we set the width here to make ListViews work correctly.
  6038. // This is not needed for DataViews
  6039. width: el.getWidth(!Ext.isIE || Ext.isStrict),
  6040. height: el.getHeight(!Ext.isIE || Ext.isStrict)
  6041. });
  6042. }
  6043. });
  6044. // get new positions
  6045. var index = 0;
  6046. Ext.iterate(store.data.items, function(item) {
  6047. var id = item.get(idProperty),
  6048. column = index % columns,
  6049. row = Math.floor(index / columns),
  6050. top = row * itemHeight,
  6051. left = column * itemWidth;
  6052. newPositions[id] = {
  6053. top: top,
  6054. left: left
  6055. };
  6056. index++;
  6057. }, this);
  6058. // do the movements
  6059. var startTime = new Date(),
  6060. duration = this.duration,
  6061. dataviewID = this.dataviewID,
  6062. doAnimate = function() {
  6063. var elapsed = new Date() - startTime,
  6064. fraction = elapsed / duration,
  6065. id;
  6066. if (fraction >= 1) {
  6067. for (id in newPositions) {
  6068. Ext.fly(dataviewID + '-' + id).applyStyles({
  6069. top: newPositions[id].top + "px",
  6070. left: newPositions[id].left + "px"
  6071. });
  6072. }
  6073. Ext.TaskManager.stop(task);
  6074. } else {
  6075. // move each item
  6076. for (id in newPositions) {
  6077. if (!previous[id]) {
  6078. continue;
  6079. }
  6080. var oldPos = oldPositions[id],
  6081. newPos = newPositions[id],
  6082. oldTop = oldPos.top,
  6083. newTop = newPos.top,
  6084. oldLeft = oldPos.left,
  6085. newLeft = newPos.left,
  6086. diffTop = fraction * Math.abs(oldTop - newTop),
  6087. diffLeft = fraction * Math.abs(oldLeft - newLeft),
  6088. midTop = oldTop > newTop ? oldTop - diffTop : oldTop + diffTop,
  6089. midLeft = oldLeft > newLeft ? oldLeft - diffLeft : oldLeft + diffLeft;
  6090. Ext.fly(dataviewID + '-' + id).applyStyles({
  6091. top: midTop + "px",
  6092. left: midLeft + "px"
  6093. });
  6094. }
  6095. }
  6096. },
  6097. task = {
  6098. run: doAnimate,
  6099. interval: 20,
  6100. scope: this
  6101. };
  6102. Ext.TaskManager.start(task);
  6103. //<debug>
  6104. var count = 0;
  6105. for (var k in added) {
  6106. // eslint-disable-line no-unused-vars
  6107. count++;
  6108. }
  6109. if (Ext.global.console && Ext.global.console.log) {
  6110. Ext.global.console.log('added:', count);
  6111. }
  6112. //</debug>
  6113. // show new items
  6114. Ext.iterate(added, function(id, item) {
  6115. Ext.fly(this.dataviewID + '-' + item.get(this.idProperty)).applyStyles({
  6116. top: newPositions[item.get(this.idProperty)].top + "px",
  6117. left: newPositions[item.get(this.idProperty)].left + "px"
  6118. });
  6119. Ext.fly(this.dataviewID + '-' + item.get(this.idProperty)).animate({
  6120. remove: false,
  6121. duration: duration,
  6122. opacity: 1
  6123. });
  6124. }, this);
  6125. this.cacheStoreData(store);
  6126. }, this);
  6127. },
  6128. /**
  6129. * Caches the records from a store locally for comparison later
  6130. * @param {Ext.data.Store} store The store to cache data from
  6131. */
  6132. cacheStoreData: function(store) {
  6133. this.cachedStoreData = {};
  6134. store.each(function(record) {
  6135. this.cachedStoreData[record.get(this.idProperty)] = record;
  6136. }, this);
  6137. },
  6138. /**
  6139. * Returns all records that were already in the DataView
  6140. * @return {Object} All existing records
  6141. */
  6142. getExisting: function() {
  6143. return this.cachedStoreData;
  6144. },
  6145. /**
  6146. * Returns the total number of items that are currently visible in the DataView
  6147. * @return {Number} The number of existing items
  6148. */
  6149. getExistingCount: function() {
  6150. var count = 0,
  6151. items = this.getExisting();
  6152. for (var k in items) {
  6153. // eslint-disable-line no-unused-vars
  6154. count++;
  6155. }
  6156. return count;
  6157. },
  6158. /**
  6159. * Returns all records in the given store that were not already present
  6160. * @param {Ext.data.Store} store The updated store instance
  6161. * @return {Object} Object of records not already present in the dataview in format {id: record}
  6162. */
  6163. getAdded: function(store) {
  6164. var added = {};
  6165. store.each(function(record) {
  6166. // eslint-disable-next-line eqeqeq
  6167. if (this.cachedStoreData[record.get(this.idProperty)] == undefined) {
  6168. added[record.get(this.idProperty)] = record;
  6169. }
  6170. }, this);
  6171. return added;
  6172. },
  6173. /**
  6174. * Returns all records that are present in the DataView but not the new store
  6175. * @param {Ext.data.Store} store The updated store instance
  6176. * @return {Array} Array of records that used to be present
  6177. */
  6178. getRemoved: function(store) {
  6179. var removed = [];
  6180. for (var id in this.cachedStoreData) {
  6181. if (store.findExact(this.idProperty, Number(id)) === -1) {
  6182. removed.push(this.cachedStoreData[id]);
  6183. }
  6184. }
  6185. return removed;
  6186. },
  6187. /**
  6188. * Returns all records that are already present and are still present in the new store
  6189. * @param {Ext.data.Store} store The updated store instance
  6190. * @return {Object} Object of records that are still present from last time in format
  6191. * {id: record}
  6192. */
  6193. getRemaining: function(store) {
  6194. var remaining = {};
  6195. store.each(function(record) {
  6196. /* eslint-disable-next-line eqeqeq */
  6197. if (this.cachedStoreData[record.get(this.idProperty)] != undefined) {
  6198. remaining[record.get(this.idProperty)] = record;
  6199. }
  6200. }, this);
  6201. return remaining;
  6202. }
  6203. });
  6204. /**
  6205. * An explorer component for navigating hierarchical content. Consists of a breadcrumb bar
  6206. * at the top, tree navigation on the left, and a center panel which displays the contents
  6207. * of a given node.
  6208. */
  6209. Ext.define('Ext.ux.Explorer', {
  6210. extend: 'Ext.panel.Panel',
  6211. xtype: 'explorer',
  6212. requires: [
  6213. 'Ext.layout.container.Border',
  6214. 'Ext.toolbar.Breadcrumb',
  6215. 'Ext.tree.Panel'
  6216. ],
  6217. config: {
  6218. /**
  6219. * @cfg {Object} breadcrumb
  6220. * Configuration object for the breadcrumb toolbar
  6221. */
  6222. breadcrumb: {
  6223. dock: 'top',
  6224. xtype: 'breadcrumb',
  6225. reference: 'breadcrumb'
  6226. },
  6227. /* eslint-disable max-len, indent */
  6228. /**
  6229. * @cfg {Object} contentView
  6230. * Configuration object for the "content" data view
  6231. */
  6232. contentView: {
  6233. xtype: 'dataview',
  6234. reference: 'contentView',
  6235. region: 'center',
  6236. cls: Ext.baseCSSPrefix + 'explorer-view',
  6237. itemSelector: '.' + Ext.baseCSSPrefix + 'explorer-item',
  6238. tpl: '<tpl for=".">' + '<div class="' + Ext.baseCSSPrefix + 'explorer-item">' + '<div class="{iconCls}">' + '<div class="' + Ext.baseCSSPrefix + 'explorer-node-icon' + '{[values.leaf ? " ' + Ext.baseCSSPrefix + 'explorer-leaf-icon' + '" : ""]}' + '">' + '</div>' + '<div class="' + Ext.baseCSSPrefix + 'explorer-item-text">{text}</div>' + '</div>' + '</div>' + '</tpl>'
  6239. },
  6240. /* eslint-enable max-len, indent */
  6241. /**
  6242. * @cfg {Ext.data.TreeStore} store
  6243. * The TreeStore to use as the data source
  6244. */
  6245. store: null,
  6246. /**
  6247. * @cfg {Object} tree
  6248. * Configuration object for the tree
  6249. */
  6250. tree: {
  6251. xtype: 'treepanel',
  6252. reference: 'tree',
  6253. region: 'west',
  6254. width: 200
  6255. }
  6256. },
  6257. renderConfig: {
  6258. /**
  6259. * @cfg {Ext.data.TreeModel} selection
  6260. * The selected node
  6261. * @accessor
  6262. */
  6263. selection: null
  6264. },
  6265. layout: 'border',
  6266. referenceHolder: true,
  6267. defaultListenerScope: true,
  6268. cls: Ext.baseCSSPrefix + 'explorer',
  6269. initComponent: function() {
  6270. var me = this,
  6271. store = me.getStore();
  6272. //<debug>
  6273. if (!store) {
  6274. Ext.raise('Ext.ux.Explorer requires a store.');
  6275. }
  6276. //</debug>
  6277. me.dockedItems = [
  6278. me.getBreadcrumb()
  6279. ];
  6280. me.items = [
  6281. me.getTree(),
  6282. me.getContentView()
  6283. ];
  6284. me.callParent();
  6285. },
  6286. applyBreadcrumb: function(breadcrumb) {
  6287. var store = this.getStore();
  6288. breadcrumb = Ext.create(Ext.apply({
  6289. store: store,
  6290. selection: store.getRoot()
  6291. }, breadcrumb));
  6292. breadcrumb.on('selectionchange', '_onBreadcrumbSelectionChange', this);
  6293. return breadcrumb;
  6294. },
  6295. applyContentView: function(contentView) {
  6296. /**
  6297. * @property {Ext.data.Store} contentStore
  6298. * @private
  6299. * The backing store for the content view
  6300. */
  6301. var contentStore = this.contentStore = new Ext.data.Store({
  6302. model: this.getStore().model
  6303. });
  6304. contentView = Ext.create(Ext.apply({
  6305. store: contentStore
  6306. }, contentView));
  6307. return contentView;
  6308. },
  6309. applyTree: function(tree) {
  6310. tree = Ext.create(Ext.apply({
  6311. store: this.getStore()
  6312. }, tree));
  6313. tree.on('selectionchange', '_onTreeSelectionChange', this);
  6314. return tree;
  6315. },
  6316. updateSelection: function(node) {
  6317. var me = this,
  6318. refs = me.getReferences(),
  6319. breadcrumb = refs.breadcrumb,
  6320. tree = refs.tree,
  6321. treeSelectionModel = tree.getSelectionModel(),
  6322. contentStore = me.contentStore,
  6323. parentNode, treeView;
  6324. if (breadcrumb.getSelection() !== node) {
  6325. breadcrumb.setSelection(node);
  6326. }
  6327. if (treeSelectionModel.getSelection()[0] !== node) {
  6328. treeSelectionModel.select([
  6329. node
  6330. ]);
  6331. parentNode = node.parentNode;
  6332. if (parentNode) {
  6333. parentNode.expand();
  6334. }
  6335. treeView = tree.getView();
  6336. treeView.scrollRowIntoView(treeView.getRow(node));
  6337. }
  6338. contentStore.removeAll();
  6339. contentStore.add(node.hasChildNodes() ? node.childNodes : [
  6340. node
  6341. ]);
  6342. },
  6343. updateStore: function(store) {
  6344. this.getBreadcrumb().setStore(store);
  6345. },
  6346. privates: {
  6347. /**
  6348. * Handles the tree's selectionchange event
  6349. * @private
  6350. * @param {Ext.tree.Panel} tree
  6351. * @param {Ext.data.TreeModel[]} selection
  6352. */
  6353. _onTreeSelectionChange: function(tree, selection) {
  6354. this.setSelection(selection[0]);
  6355. },
  6356. /**
  6357. * Handles the breadcrumb bar's selectionchange event
  6358. */
  6359. _onBreadcrumbSelectionChange: function(breadcrumb, selection) {
  6360. this.setSelection(selection);
  6361. }
  6362. }
  6363. });
  6364. /**
  6365. * A plugin for Field Components which creates clones of the Field for as
  6366. * long as the user keeps filling them. Leaving the final one blank ends the repeating series.
  6367. *
  6368. * Usage:
  6369. *
  6370. * items: [{
  6371. * xtype: 'combo',
  6372. * plugins: {
  6373. * fieldreplicator: true
  6374. * },
  6375. * triggerAction: 'all',
  6376. * fieldLabel: 'Select recipient',
  6377. * store: recipientStore
  6378. * }]
  6379. *
  6380. */
  6381. Ext.define('Ext.ux.FieldReplicator', {
  6382. alias: 'plugin.fieldreplicator',
  6383. init: function(field) {
  6384. // Assign the field an id grouping it with fields cloned from it. If it already
  6385. // has an id that means it is itself a clone.
  6386. if (!field.replicatorId) {
  6387. field.replicatorId = Ext.id();
  6388. }
  6389. field.on('blur', this.onBlur, this);
  6390. },
  6391. onBlur: function(field) {
  6392. var ownerCt = field.ownerCt,
  6393. replicatorId = field.replicatorId,
  6394. isEmpty = Ext.isEmpty(field.getRawValue()),
  6395. siblings = ownerCt.query('[replicatorId=' + replicatorId + ']'),
  6396. isLastInGroup = siblings[siblings.length - 1] === field,
  6397. clone, idx;
  6398. // If a field before the final one was blanked out, remove it
  6399. if (isEmpty && !isLastInGroup) {
  6400. Ext.defer(field.destroy, 10, field);
  6401. }
  6402. // delay to allow tab key to move focus first
  6403. // If the field is the last in the list and has a value, add a cloned field after it
  6404. else if (!isEmpty && isLastInGroup) {
  6405. if (field.onReplicate) {
  6406. field.onReplicate();
  6407. }
  6408. clone = field.cloneConfig({
  6409. replicatorId: replicatorId
  6410. });
  6411. idx = ownerCt.items.indexOf(field);
  6412. ownerCt.add(idx + 1, clone);
  6413. }
  6414. }
  6415. });
  6416. /**
  6417. * The GMap Panel UX extends `Ext.panel.Panel` in order to display Google Maps.
  6418. *
  6419. * It is important to note that you must include the following Google Maps API above
  6420. * bootstrap.js in your application's index.html file (or equivalent).
  6421. *
  6422. * <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?v=3&sensor=false"></script>
  6423. *
  6424. * It is important to note that due to the Google Maps loader, you cannot currently include
  6425. * the above JS resource in the Cmd generated app.json file. Doing so interferes with the loading of
  6426. * Ext JS and Google Maps.
  6427. *
  6428. * The following example creates a window containing a GMap Panel. In this case, the center
  6429. * is set as geoCodeAddr, which is a string that Google translates into longitude and latitude.
  6430. *
  6431. * var mapwin = Ext.create('Ext.Window', {
  6432. * layout: 'fit',
  6433. * title: 'GMap Window',
  6434. * width: 450,
  6435. * height: 250,
  6436. * items: {
  6437. * xtype: 'gmappanel',
  6438. * gmapType: 'map',
  6439. * center: {
  6440. * geoCodeAddr: "221B Baker Street",
  6441. * marker: {
  6442. * title: 'Holmes Home'
  6443. * }
  6444. * },
  6445. * mapOptions : {
  6446. * mapTypeId: google.maps.MapTypeId.ROADMAP
  6447. * }
  6448. * }
  6449. * }).show();
  6450. *
  6451. */
  6452. Ext.define('Ext.ux.GMapPanel', {
  6453. extend: 'Ext.panel.Panel',
  6454. alias: 'widget.gmappanel',
  6455. requires: [
  6456. 'Ext.window.MessageBox'
  6457. ],
  6458. initComponent: function() {
  6459. Ext.applyIf(this, {
  6460. plain: true,
  6461. gmapType: 'map',
  6462. border: false
  6463. });
  6464. this.callParent();
  6465. },
  6466. onBoxReady: function() {
  6467. var center = this.center;
  6468. this.callParent(arguments);
  6469. if (center) {
  6470. if (center.geoCodeAddr) {
  6471. this.lookupCode(center.geoCodeAddr, center.marker);
  6472. } else {
  6473. this.createMap(center);
  6474. }
  6475. } else {
  6476. Ext.raise('center is required');
  6477. }
  6478. },
  6479. createMap: function(center, marker) {
  6480. var options = Ext.apply({}, this.mapOptions);
  6481. /* global google */
  6482. options = Ext.applyIf(options, {
  6483. zoom: 14,
  6484. center: center,
  6485. mapTypeId: google.maps.MapTypeId.HYBRID
  6486. });
  6487. this.gmap = new google.maps.Map(this.body.dom, options);
  6488. if (marker) {
  6489. this.addMarker(Ext.applyIf(marker, {
  6490. position: center
  6491. }));
  6492. }
  6493. Ext.each(this.markers, this.addMarker, this);
  6494. this.fireEvent('mapready', this, this.gmap);
  6495. },
  6496. addMarker: function(marker) {
  6497. var o;
  6498. marker = Ext.apply({
  6499. map: this.gmap
  6500. }, marker);
  6501. if (!marker.position) {
  6502. marker.position = new google.maps.LatLng(marker.lat, marker.lng);
  6503. }
  6504. o = new google.maps.Marker(marker);
  6505. Ext.Object.each(marker.listeners, function(name, fn) {
  6506. google.maps.event.addListener(o, name, fn);
  6507. });
  6508. return o;
  6509. },
  6510. lookupCode: function(addr, marker) {
  6511. this.geocoder = new google.maps.Geocoder();
  6512. this.geocoder.geocode({
  6513. address: addr
  6514. }, Ext.Function.bind(this.onLookupComplete, this, [
  6515. marker
  6516. ], true));
  6517. },
  6518. onLookupComplete: function(data, response, marker) {
  6519. if (response !== 'OK') {
  6520. Ext.MessageBox.alert('Error', 'An error occured: "' + response + '"');
  6521. return;
  6522. }
  6523. this.createMap(data[0].geometry.location, marker);
  6524. },
  6525. afterComponentLayout: function(w, h) {
  6526. this.callParent(arguments);
  6527. this.redraw();
  6528. },
  6529. redraw: function() {
  6530. var map = this.gmap;
  6531. if (map) {
  6532. google.maps.event.trigger(map, 'resize');
  6533. }
  6534. }
  6535. });
  6536. /**
  6537. * Barebones iframe implementation.
  6538. */
  6539. Ext.define('Ext.ux.IFrame', {
  6540. extend: 'Ext.Component',
  6541. alias: 'widget.uxiframe',
  6542. loadMask: 'Loading...',
  6543. src: 'about:blank',
  6544. renderTpl: [
  6545. // eslint-disable-next-line max-len
  6546. '<iframe src="{src}" id="{id}-iframeEl" data-ref="iframeEl" name="{frameName}" width="100%" height="100%" frameborder="0"></iframe>'
  6547. ],
  6548. childEls: [
  6549. 'iframeEl'
  6550. ],
  6551. initComponent: function() {
  6552. this.callParent();
  6553. this.frameName = this.frameName || this.id + '-frame';
  6554. },
  6555. initEvents: function() {
  6556. var me = this;
  6557. me.callParent();
  6558. me.iframeEl.on('load', me.onLoad, me);
  6559. },
  6560. initRenderData: function() {
  6561. return Ext.apply(this.callParent(), {
  6562. src: this.src,
  6563. frameName: this.frameName
  6564. });
  6565. },
  6566. getBody: function() {
  6567. var doc = this.getDoc();
  6568. return doc.body || doc.documentElement;
  6569. },
  6570. getDoc: function() {
  6571. try {
  6572. return this.getWin().document;
  6573. } catch (ex) {
  6574. return null;
  6575. }
  6576. },
  6577. getWin: function() {
  6578. var me = this,
  6579. name = me.frameName,
  6580. win = Ext.isIE ? me.iframeEl.dom.contentWindow : window.frames[name];
  6581. return win;
  6582. },
  6583. getFrame: function() {
  6584. var me = this;
  6585. return me.iframeEl.dom;
  6586. },
  6587. onLoad: function() {
  6588. var me = this,
  6589. doc = me.getDoc();
  6590. if (doc) {
  6591. this.el.unmask();
  6592. this.fireEvent('load', this);
  6593. } else if (me.src) {
  6594. this.el.unmask();
  6595. this.fireEvent('error', this);
  6596. }
  6597. },
  6598. load: function(src) {
  6599. var me = this,
  6600. text = me.loadMask,
  6601. frame = me.getFrame();
  6602. if (me.fireEvent('beforeload', me, src) !== false) {
  6603. if (text && me.el) {
  6604. me.el.mask(text);
  6605. }
  6606. frame.src = me.src = (src || me.src);
  6607. }
  6608. }
  6609. });
  6610. /*
  6611. * Note: Event relayers are not needed here because the combination of the gesture system and
  6612. * normal focus/blur will handle it.
  6613. * Tested with the examples/classic/desktop app.
  6614. */
  6615. /*
  6616. * TODO items:
  6617. *
  6618. * Iframe should clean up any Ext.dom.Element wrappers around its window, document
  6619. * documentElement and body when it is destroyed. This helps prevent "Permission Denied"
  6620. * errors in IE when Ext.dom.GarbageCollector tries to access those objects on an orphaned
  6621. * iframe. Permission Denied errors can occur in one of the following 2 scenarios:
  6622. *
  6623. * a. When an iframe is removed from the document, and all references to it have been
  6624. * removed, IE will "clear" the window object. At this point the window object becomes
  6625. * completely inaccessible - accessing any of its properties results in a "Permission
  6626. * Denied" error. http://msdn.microsoft.com/en-us/library/ie/hh180174(v=vs.85).aspx
  6627. *
  6628. * b. When an iframe is unloaded (either by navigating to a new url, or via document.open/
  6629. * document.write, new html and body elements are created and the old the html and body
  6630. * elements are orphaned. Accessing the html and body elements or any of their properties
  6631. * results in a "Permission Denied" error.
  6632. */
  6633. /**
  6634. * Basic status bar component that can be used as the bottom toolbar of any {@link Ext.Panel}.
  6635. * In addition to supporting the standard {@link Ext.toolbar.Toolbar} interface for adding buttons,
  6636. * menus and other items, the StatusBar provides a greedy status element that can be aligned
  6637. * to either side and has convenient methods for setting the status text and icon. You can also
  6638. * indicate that something is processing using the {@link #showBusy} method.
  6639. *
  6640. * Ext.create('Ext.Panel', {
  6641. * title: 'StatusBar',
  6642. * // etc.
  6643. * bbar: Ext.create('Ext.ux.StatusBar', {
  6644. * id: 'my-status',
  6645. *
  6646. * // defaults to use when the status is cleared:
  6647. * defaultText: 'Default status text',
  6648. * defaultIconCls: 'default-icon',
  6649. *
  6650. * // values to set initially:
  6651. * text: 'Ready',
  6652. * iconCls: 'ready-icon',
  6653. *
  6654. * // any standard Toolbar items:
  6655. * items: [{
  6656. * text: 'A Button'
  6657. * }, '-', 'Plain Text']
  6658. * })
  6659. * });
  6660. *
  6661. * // Update the status bar later in code:
  6662. * var sb = Ext.getCmp('my-status');
  6663. * sb.setStatus({
  6664. * text: 'OK',
  6665. * iconCls: 'ok-icon',
  6666. * clear: true // auto-clear after a set interval
  6667. * });
  6668. *
  6669. * // Set the status bar to show that something is processing:
  6670. * sb.showBusy();
  6671. *
  6672. * // processing....
  6673. *
  6674. * sb.clearStatus(); // once completeed
  6675. *
  6676. */
  6677. Ext.define('Ext.ux.statusbar.StatusBar', {
  6678. extend: 'Ext.toolbar.Toolbar',
  6679. xtype: 'statusbar',
  6680. alternateClassName: 'Ext.ux.StatusBar',
  6681. requires: [
  6682. 'Ext.toolbar.TextItem'
  6683. ],
  6684. /**
  6685. * @cfg {String} statusAlign
  6686. * The alignment of the status element within the overall StatusBar layout. When the StatusBar
  6687. * is rendered, it creates an internal div containing the status text and icon. Any additional
  6688. * Toolbar items added in the StatusBar's {@link #cfg-items} config, or added via
  6689. * {@link #method-add} or any of the supported add* methods, will be rendered, in added order,
  6690. * to the opposite side. The status element is greedy, so it will automatically expand
  6691. * to take up all sapce left over by any other items. Example usage:
  6692. *
  6693. * // Create a left-aligned status bar containing a button,
  6694. * // separator and text item that will be right-aligned (default):
  6695. * Ext.create('Ext.Panel', {
  6696. * title: 'StatusBar',
  6697. * // etc.
  6698. * bbar: Ext.create('Ext.ux.statusbar.StatusBar', {
  6699. * defaultText: 'Default status text',
  6700. * id: 'status-id',
  6701. * items: [{
  6702. * text: 'A Button'
  6703. * }, '-', 'Plain Text']
  6704. * })
  6705. * });
  6706. *
  6707. * // By adding the statusAlign config, this will create the
  6708. * // exact same toolbar, except the status and toolbar item
  6709. * // layout will be reversed from the previous example:
  6710. * Ext.create('Ext.Panel', {
  6711. * title: 'StatusBar',
  6712. * // etc.
  6713. * bbar: Ext.create('Ext.ux.statusbar.StatusBar', {
  6714. * defaultText: 'Default status text',
  6715. * id: 'status-id',
  6716. * statusAlign: 'right',
  6717. * items: [{
  6718. * text: 'A Button'
  6719. * }, '-', 'Plain Text']
  6720. * })
  6721. * });
  6722. */
  6723. /**
  6724. * @cfg {String} [defaultText='']
  6725. * The default {@link #text} value. This will be used anytime the status bar is cleared
  6726. * with the `useDefaults:true` option.
  6727. */
  6728. /**
  6729. * @cfg {String} [defaultIconCls='']
  6730. * The default {@link #iconCls} value (see the iconCls docs for additional details about
  6731. * customizing the icon). This will be used anytime the status bar is cleared with the
  6732. * `useDefaults:true` option.
  6733. */
  6734. /**
  6735. * @cfg {String} text
  6736. * A string that will be <b>initially</b> set as the status message. This string
  6737. * will be set as innerHTML (html tags are accepted) for the toolbar item.
  6738. * If not specified, the value set for {@link #defaultText} will be used.
  6739. */
  6740. /**
  6741. * @cfg [iconCls='']
  6742. * @inheritdoc Ext.panel.Header#cfg-iconCls
  6743. * @localdoc **Note:** This CSS class will be **initially** set as the status bar
  6744. * icon. See also {@link #defaultIconCls} and {@link #busyIconCls}.
  6745. *
  6746. * Example usage:
  6747. *
  6748. * // Example CSS rule:
  6749. * .x-statusbar .x-status-custom {
  6750. * padding-left: 25px;
  6751. * background: transparent url(images/custom-icon.gif) no-repeat 3px 2px;
  6752. * }
  6753. *
  6754. * // Setting a default icon:
  6755. * var sb = Ext.create('Ext.ux.statusbar.StatusBar', {
  6756. * defaultIconCls: 'x-status-custom'
  6757. * });
  6758. *
  6759. * // Changing the icon:
  6760. * sb.setStatus({
  6761. * text: 'New status',
  6762. * iconCls: 'x-status-custom'
  6763. * });
  6764. */
  6765. /**
  6766. * @cfg {String} cls
  6767. * The base class applied to the containing element for this component on render.
  6768. */
  6769. cls: 'x-statusbar',
  6770. /**
  6771. * @cfg {String} busyIconCls
  6772. * The default {@link #iconCls} applied when calling {@link #showBusy}.
  6773. * It can be overridden at any time by passing the `iconCls` argument into {@link #showBusy}.
  6774. */
  6775. busyIconCls: 'x-status-busy',
  6776. /**
  6777. * @cfg {String} busyText
  6778. * The default {@link #text} applied when calling {@link #showBusy}.
  6779. * It can be overridden at any time by passing the `text` argument into {@link #showBusy}.
  6780. */
  6781. busyText: 'Loading...',
  6782. /**
  6783. * @cfg {Number} autoClear
  6784. * The number of milliseconds to wait after setting the status via
  6785. * {@link #setStatus} before automatically clearing the status text and icon.
  6786. * Note that this only applies when passing the `clear` argument to {@link #setStatus}
  6787. * since that is the only way to defer clearing the status. This can
  6788. * be overridden by specifying a different `wait` value in {@link #setStatus}.
  6789. * Calls to {@link #clearStatus} always clear the status bar immediately and ignore this value.
  6790. */
  6791. autoClear: 5000,
  6792. /**
  6793. * @cfg {String} emptyText
  6794. * The text string to use if no text has been set. If there are no other items in
  6795. * the toolbar using an empty string (`''`) for this value would end up in the toolbar
  6796. * height collapsing since the empty string will not maintain the toolbar height.
  6797. * Use `''` if the toolbar should collapse in height vertically when no text is
  6798. * specified and there are no other items in the toolbar.
  6799. */
  6800. emptyText: '&#160;',
  6801. /**
  6802. * @private
  6803. */
  6804. activeThreadId: 0,
  6805. initComponent: function() {
  6806. var right = this.statusAlign === 'right';
  6807. this.callParent(arguments);
  6808. this.currIconCls = this.iconCls || this.defaultIconCls;
  6809. this.statusEl = Ext.create('Ext.toolbar.TextItem', {
  6810. cls: 'x-status-text ' + (this.currIconCls || ''),
  6811. text: this.text || this.defaultText || ''
  6812. });
  6813. if (right) {
  6814. this.cls += ' x-status-right';
  6815. this.add('->');
  6816. this.add(this.statusEl);
  6817. } else {
  6818. this.insert(0, this.statusEl);
  6819. this.insert(1, '->');
  6820. }
  6821. },
  6822. /**
  6823. * Sets the status {@link #text} and/or {@link #iconCls}. Also supports automatically clearing
  6824. * the status that was set after a specified interval.
  6825. *
  6826. * Example usage:
  6827. *
  6828. * // Simple call to update the text
  6829. * statusBar.setStatus('New status');
  6830. *
  6831. * // Set the status and icon, auto-clearing with default options:
  6832. * statusBar.setStatus({
  6833. * text: 'New status',
  6834. * iconCls: 'x-status-custom',
  6835. * clear: true
  6836. * });
  6837. *
  6838. * // Auto-clear with custom options:
  6839. * statusBar.setStatus({
  6840. * text: 'New status',
  6841. * iconCls: 'x-status-custom',
  6842. * clear: {
  6843. * wait: 8000,
  6844. * anim: false,
  6845. * useDefaults: false
  6846. * }
  6847. * });
  6848. *
  6849. * @param {Object/String} config A config object specifying what status to set, or a string
  6850. * assumed to be the status text (and all other options are defaulted as explained below).
  6851. * A config object containing any or all of the following properties can be passed:
  6852. *
  6853. * @param {String} config.text The status text to display. If not specified, any current
  6854. * status text will remain unchanged.
  6855. *
  6856. * @param {String} config.iconCls The CSS class used to customize the status icon (see
  6857. * {@link #iconCls} for details). If not specified, any current iconCls will remain unchanged.
  6858. *
  6859. * @param {Boolean/Number/Object} config.clear Allows you to set an internal callback that will
  6860. * automatically clear the status text and iconCls after a specified amount of time has passed.
  6861. * If clear is not specified, the new status will not be auto-cleared and will stay until
  6862. * updated again or cleared using {@link #clearStatus}. If `true` is passed, the status will be
  6863. * cleared using {@link #autoClear}, {@link #defaultText} and {@link #defaultIconCls} via
  6864. * a fade out animation. If a numeric value is passed, it will be used as the callback interval
  6865. * (in milliseconds), overriding the {@link #autoClear} value. All other options will be
  6866. * defaulted as with the boolean option. To customize any other options, you can pass an object
  6867. * in the format:
  6868. *
  6869. * @param {Number} config.clear.wait The number of milliseconds to wait before clearing
  6870. * (defaults to {@link #autoClear}).
  6871. * @param {Boolean} config.clear.anim False to clear the status immediately once the callback
  6872. * executes (defaults to true which fades the status out).
  6873. * @param {Boolean} config.clear.useDefaults False to completely clear the status text
  6874. * and iconCls (defaults to true which uses {@link #defaultText} and {@link #defaultIconCls}).
  6875. *
  6876. * @return {Ext.ux.statusbar.StatusBar} this
  6877. */
  6878. setStatus: function(config) {
  6879. var me = this,
  6880. c, wait, defaults;
  6881. config = config || {};
  6882. Ext.suspendLayouts();
  6883. if (Ext.isString(config)) {
  6884. config = {
  6885. text: config
  6886. };
  6887. }
  6888. if (config.text !== undefined) {
  6889. me.setText(config.text);
  6890. }
  6891. if (config.iconCls !== undefined) {
  6892. me.setIcon(config.iconCls);
  6893. }
  6894. if (config.clear) {
  6895. c = config.clear;
  6896. wait = me.autoClear;
  6897. defaults = {
  6898. useDefaults: true,
  6899. anim: true
  6900. };
  6901. if (Ext.isObject(c)) {
  6902. c = Ext.applyIf(c, defaults);
  6903. if (c.wait) {
  6904. wait = c.wait;
  6905. }
  6906. } else if (Ext.isNumber(c)) {
  6907. wait = c;
  6908. c = defaults;
  6909. } else if (Ext.isBoolean(c)) {
  6910. c = defaults;
  6911. }
  6912. c.threadId = this.activeThreadId;
  6913. Ext.defer(me.clearStatus, wait, me, [
  6914. c
  6915. ]);
  6916. }
  6917. Ext.resumeLayouts(true);
  6918. return me;
  6919. },
  6920. /**
  6921. * Clears the status {@link #text} and {@link #iconCls}. Also supports clearing via an optional
  6922. * fade out animation.
  6923. *
  6924. * @param {Object} [config] A config object containing any or all of the following properties.
  6925. * If this object is not specified the status will be cleared using the defaults below:
  6926. * @param {Boolean} config.anim True to clear the status by fading out the status element
  6927. * (defaults to false which clears immediately).
  6928. * @param {Boolean} config.useDefaults True to reset the text and icon using
  6929. * {@link #defaultText} and {@link #defaultIconCls} (defaults to false which sets the text
  6930. * to '' and removes any existing icon class).
  6931. *
  6932. * @return {Ext.ux.statusbar.StatusBar} this
  6933. */
  6934. clearStatus: function(config) {
  6935. var me = this,
  6936. statusEl = me.statusEl,
  6937. text, iconCls;
  6938. config = config || {};
  6939. if (me.destroyed || config.threadId && config.threadId !== me.activeThreadId) {
  6940. // this means the current call was made internally, but a newer
  6941. // thread has set a message since this call was deferred. Since
  6942. // we don't want to overwrite a newer message just ignore.
  6943. return me;
  6944. }
  6945. text = config.useDefaults ? me.defaultText : me.emptyText;
  6946. iconCls = config.useDefaults ? (me.defaultIconCls ? me.defaultIconCls : '') : '';
  6947. if (config.anim) {
  6948. // animate the statusEl Ext.Element
  6949. statusEl.el.puff({
  6950. remove: false,
  6951. useDisplay: true,
  6952. callback: function() {
  6953. statusEl.el.show();
  6954. me.setStatus({
  6955. text: text,
  6956. iconCls: iconCls
  6957. });
  6958. }
  6959. });
  6960. } else {
  6961. me.setStatus({
  6962. text: text,
  6963. iconCls: iconCls
  6964. });
  6965. }
  6966. return me;
  6967. },
  6968. /**
  6969. * Convenience method for setting the status text directly. For more flexible options see
  6970. * {@link #setStatus}.
  6971. * @param {String} text (optional) The text to set (defaults to '')
  6972. * @return {Ext.ux.statusbar.StatusBar} this
  6973. */
  6974. setText: function(text) {
  6975. var me = this;
  6976. me.activeThreadId++;
  6977. me.text = text || '';
  6978. if (me.rendered) {
  6979. me.statusEl.setText(me.text);
  6980. }
  6981. return me;
  6982. },
  6983. /**
  6984. * Returns the current status text.
  6985. * @return {String} The status text
  6986. */
  6987. getText: function() {
  6988. return this.text;
  6989. },
  6990. /**
  6991. * Convenience method for setting the status icon directly. For more flexible options see
  6992. * {@link #setStatus}.
  6993. * See {@link #iconCls} for complete details about customizing the icon.
  6994. * @param {String} cls (optional) The icon class to set (defaults to '', and any current
  6995. * icon class is removed)
  6996. * @return {Ext.ux.statusbar.StatusBar} this
  6997. */
  6998. setIcon: function(cls) {
  6999. var me = this;
  7000. me.activeThreadId++;
  7001. cls = cls || '';
  7002. if (me.rendered) {
  7003. if (me.currIconCls) {
  7004. me.statusEl.removeCls(me.currIconCls);
  7005. me.currIconCls = null;
  7006. }
  7007. if (cls.length > 0) {
  7008. me.statusEl.addCls(cls);
  7009. me.currIconCls = cls;
  7010. }
  7011. } else {
  7012. me.currIconCls = cls;
  7013. }
  7014. return me;
  7015. },
  7016. /**
  7017. * Convenience method for setting the status text and icon to special values that are
  7018. * pre-configured to indicate a "busy" state, usually for loading or processing activities.
  7019. *
  7020. * @param {Object/String} config (optional) A config object in the same format supported by
  7021. * {@link #setStatus}, or a string to use as the status text (in which case all other options
  7022. * for setStatus will be defaulted). Use the `text` and/or `iconCls` properties on the config
  7023. * to override the default {@link #busyText} and {@link #busyIconCls} settings. If the config
  7024. * argument is not specified, {@link #busyText} and {@link #busyIconCls} will be used
  7025. * in conjunction with all of the default options for {@link #setStatus}.
  7026. * @return {Ext.ux.statusbar.StatusBar} this
  7027. */
  7028. showBusy: function(config) {
  7029. if (Ext.isString(config)) {
  7030. config = {
  7031. text: config
  7032. };
  7033. }
  7034. config = Ext.applyIf(config || {}, {
  7035. text: this.busyText,
  7036. iconCls: this.busyIconCls
  7037. });
  7038. return this.setStatus(config);
  7039. }
  7040. });
  7041. /**
  7042. * A GridPanel class with live search support.
  7043. */
  7044. Ext.define('Ext.ux.LiveSearchGridPanel', {
  7045. extend: 'Ext.grid.Panel',
  7046. requires: [
  7047. 'Ext.toolbar.TextItem',
  7048. 'Ext.form.field.Checkbox',
  7049. 'Ext.form.field.Text',
  7050. 'Ext.ux.statusbar.StatusBar'
  7051. ],
  7052. /**
  7053. * @private
  7054. * search value initialization
  7055. */
  7056. searchValue: null,
  7057. /**
  7058. * @private
  7059. * The matched positions from the most recent search
  7060. */
  7061. matches: [],
  7062. /**
  7063. * @private
  7064. * The current index matched.
  7065. */
  7066. currentIndex: null,
  7067. /**
  7068. * @private
  7069. * The generated regular expression used for searching.
  7070. */
  7071. searchRegExp: null,
  7072. /**
  7073. * @private
  7074. * Case sensitive mode.
  7075. */
  7076. caseSensitive: false,
  7077. /**
  7078. * @private
  7079. * Regular expression mode.
  7080. */
  7081. regExpMode: false,
  7082. /**
  7083. * @cfg {String} matchCls
  7084. * The matched string css classe.
  7085. */
  7086. matchCls: 'x-livesearch-match',
  7087. defaultStatusText: 'Nothing Found',
  7088. // Component initialization override: adds the top and bottom toolbars and setup
  7089. // headers renderer.
  7090. initComponent: function() {
  7091. var me = this;
  7092. me.tbar = [
  7093. 'Search',
  7094. {
  7095. xtype: 'textfield',
  7096. name: 'searchField',
  7097. hideLabel: true,
  7098. width: 200,
  7099. listeners: {
  7100. change: {
  7101. fn: me.onTextFieldChange,
  7102. scope: this,
  7103. buffer: 500
  7104. }
  7105. }
  7106. },
  7107. {
  7108. xtype: 'button',
  7109. text: '&lt;',
  7110. tooltip: 'Find Previous Row',
  7111. handler: me.onPreviousClick,
  7112. scope: me
  7113. },
  7114. {
  7115. xtype: 'button',
  7116. text: '&gt;',
  7117. tooltip: 'Find Next Row',
  7118. handler: me.onNextClick,
  7119. scope: me
  7120. },
  7121. '-',
  7122. {
  7123. xtype: 'checkbox',
  7124. hideLabel: true,
  7125. margin: '0 0 0 4px',
  7126. handler: me.regExpToggle,
  7127. scope: me
  7128. },
  7129. 'Regular expression',
  7130. {
  7131. xtype: 'checkbox',
  7132. hideLabel: true,
  7133. margin: '0 0 0 4px',
  7134. handler: me.caseSensitiveToggle,
  7135. scope: me
  7136. },
  7137. 'Case sensitive'
  7138. ];
  7139. me.bbar = new Ext.ux.StatusBar({
  7140. defaultText: me.defaultStatusText,
  7141. name: 'searchStatusBar'
  7142. });
  7143. me.callParent(arguments);
  7144. },
  7145. // afterRender override: it adds textfield and statusbar reference and start monitoring
  7146. // keydown events in textfield input
  7147. afterRender: function() {
  7148. var me = this;
  7149. me.callParent(arguments);
  7150. me.textField = me.down('textfield[name=searchField]');
  7151. me.statusBar = me.down('statusbar[name=searchStatusBar]');
  7152. me.view.on('cellkeydown', me.focusTextField, me);
  7153. },
  7154. focusTextField: function(view, td, cellIndex, record, tr, rowIndex, e, eOpts) {
  7155. if (e.getKey() === e.S) {
  7156. e.preventDefault();
  7157. this.textField.focus();
  7158. }
  7159. },
  7160. // detects html tag
  7161. tagsRe: /<[^>]*>/gm,
  7162. // DEL ASCII code
  7163. tagsProtect: '\x0f',
  7164. /**
  7165. * In normal mode it returns the value with protected regexp characters.
  7166. * In regular expression mode it returns the raw value except if the regexp is invalid.
  7167. * @return {String} The value to process or null if the textfield value is blank or invalid.
  7168. * @private
  7169. */
  7170. getSearchValue: function() {
  7171. var me = this,
  7172. value = me.textField.getValue();
  7173. if (value === '') {
  7174. return null;
  7175. }
  7176. if (!me.regExpMode) {
  7177. value = Ext.String.escapeRegex(value);
  7178. } else {
  7179. try {
  7180. new RegExp(value);
  7181. } catch (error) {
  7182. me.statusBar.setStatus({
  7183. text: error.message,
  7184. iconCls: 'x-status-error'
  7185. });
  7186. return null;
  7187. }
  7188. // this is stupid
  7189. if (value === '^' || value === '$') {
  7190. return null;
  7191. }
  7192. }
  7193. return value;
  7194. },
  7195. /**
  7196. * Finds all strings that matches the searched value in each grid cells.
  7197. * @private
  7198. */
  7199. onTextFieldChange: function() {
  7200. var me = this,
  7201. count = 0,
  7202. view = me.view,
  7203. columns = me.visibleColumnManager.getColumns();
  7204. view.refresh();
  7205. // reset the statusbar
  7206. me.statusBar.setStatus({
  7207. text: me.defaultStatusText,
  7208. iconCls: ''
  7209. });
  7210. me.searchValue = me.getSearchValue();
  7211. me.matches = [];
  7212. me.currentIndex = null;
  7213. if (me.searchValue !== null) {
  7214. me.searchRegExp = new RegExp(me.getSearchValue(), 'g' + (me.caseSensitive ? '' : 'i'));
  7215. me.store.each(function(record, idx) {
  7216. var node = view.getNode(record);
  7217. if (node) {
  7218. Ext.Array.forEach(columns, function(column) {
  7219. var cell = Ext.fly(node).down(column.getCellInnerSelector(), true),
  7220. matches, cellHTML, seen;
  7221. if (cell) {
  7222. matches = cell.innerHTML.match(me.tagsRe);
  7223. cellHTML = cell.innerHTML.replace(me.tagsRe, me.tagsProtect);
  7224. // populate indexes array, set currentIndex, and replace wrap
  7225. // matched string in a span
  7226. cellHTML = cellHTML.replace(me.searchRegExp, function(m) {
  7227. ++count;
  7228. if (!seen) {
  7229. me.matches.push({
  7230. record: record,
  7231. column: column
  7232. });
  7233. seen = true;
  7234. }
  7235. return '<span class="' + me.matchCls + '">' + m + '</span>';
  7236. }, me);
  7237. // restore protected tags
  7238. Ext.each(matches, function(match) {
  7239. cellHTML = cellHTML.replace(me.tagsProtect, match);
  7240. });
  7241. // update cell html
  7242. cell.innerHTML = cellHTML;
  7243. }
  7244. });
  7245. }
  7246. }, me);
  7247. // results found
  7248. if (count) {
  7249. me.currentIndex = 0;
  7250. me.gotoCurrent();
  7251. me.statusBar.setStatus({
  7252. text: Ext.String.format('{0} match{1} found.', count, count === 1 ? 'es' : ''),
  7253. iconCls: 'x-status-valid'
  7254. });
  7255. }
  7256. }
  7257. // no results found
  7258. if (me.currentIndex === null) {
  7259. me.getSelectionModel().deselectAll();
  7260. me.textField.focus();
  7261. }
  7262. },
  7263. /**
  7264. * Selects the previous row containing a match.
  7265. * @private
  7266. */
  7267. onPreviousClick: function() {
  7268. var me = this,
  7269. matches = me.matches,
  7270. len = matches.length,
  7271. idx = me.currentIndex;
  7272. if (len) {
  7273. me.currentIndex = idx === 0 ? len - 1 : idx - 1;
  7274. me.gotoCurrent();
  7275. }
  7276. },
  7277. /**
  7278. * Selects the next row containing a match.
  7279. * @private
  7280. */
  7281. onNextClick: function() {
  7282. var me = this,
  7283. matches = me.matches,
  7284. len = matches.length,
  7285. idx = me.currentIndex;
  7286. if (len) {
  7287. me.currentIndex = idx === len - 1 ? 0 : idx + 1;
  7288. me.gotoCurrent();
  7289. }
  7290. },
  7291. /**
  7292. * Switch to case sensitive mode.
  7293. * @private
  7294. */
  7295. caseSensitiveToggle: function(checkbox, checked) {
  7296. this.caseSensitive = checked;
  7297. this.onTextFieldChange();
  7298. },
  7299. /**
  7300. * Switch to regular expression mode
  7301. * @private
  7302. */
  7303. regExpToggle: function(checkbox, checked) {
  7304. this.regExpMode = checked;
  7305. this.onTextFieldChange();
  7306. },
  7307. privates: {
  7308. gotoCurrent: function() {
  7309. var pos = this.matches[this.currentIndex];
  7310. this.getNavigationModel().setPosition(pos.record, pos.column);
  7311. this.getSelectionModel().select(pos.record);
  7312. }
  7313. }
  7314. });
  7315. /**
  7316. * The Preview Plugin enables toggle of a configurable preview of all visible records.
  7317. *
  7318. * Note: This plugin does NOT assert itself against an existing RowBody feature and may conflict
  7319. * with another instance of the same plugin.
  7320. */
  7321. Ext.define('Ext.ux.PreviewPlugin', {
  7322. extend: 'Ext.plugin.Abstract',
  7323. alias: 'plugin.preview',
  7324. requires: [
  7325. 'Ext.grid.feature.RowBody'
  7326. ],
  7327. /**
  7328. * @private
  7329. * css class to use to hide the body
  7330. */
  7331. hideBodyCls: 'x-grid-row-body-hidden',
  7332. /**
  7333. * @cfg {String} bodyField
  7334. * Field to display in the preview. Must be a field within the Model definition
  7335. * that the store is using.
  7336. */
  7337. bodyField: '',
  7338. /**
  7339. * @cfg {Boolean} previewExpanded
  7340. */
  7341. previewExpanded: true,
  7342. /**
  7343. * Plugin may be safely declared on either a panel.Grid or a Grid View/viewConfig
  7344. * @param {Ext.grid.Panel/Ext.view.View} target
  7345. */
  7346. setCmp: function(target) {
  7347. this.callParent(arguments);
  7348. // Resolve grid from view as necessary
  7349. // eslint-disable-next-line vars-on-top
  7350. var me = this,
  7351. grid = me.cmp = target.isXType('gridview') ? target.grid : target,
  7352. bodyField = me.bodyField,
  7353. hideBodyCls = me.hideBodyCls,
  7354. feature = Ext.create('Ext.grid.feature.RowBody', {
  7355. grid: grid,
  7356. getAdditionalData: function(data, idx, model, rowValues) {
  7357. var getAdditionalData = Ext.grid.feature.RowBody.prototype.getAdditionalData,
  7358. additionalData = {
  7359. rowBody: data[bodyField],
  7360. rowBodyCls: grid.getView().previewExpanded ? '' : hideBodyCls
  7361. };
  7362. if (Ext.isFunction(getAdditionalData)) {
  7363. // "this" is the RowBody object hjere. Do not change to "me"
  7364. Ext.apply(additionalData, getAdditionalData.apply(this, arguments));
  7365. }
  7366. return additionalData;
  7367. }
  7368. }),
  7369. initFeature = function(grid, view) {
  7370. view.previewExpanded = me.previewExpanded;
  7371. // By this point, existing features are already in place, so this must be
  7372. // initialized and added
  7373. view.featuresMC.add(feature);
  7374. feature.init(grid);
  7375. };
  7376. // The grid has already created its view
  7377. if (grid.view) {
  7378. initFeature(grid, grid.view);
  7379. } else // At the time a grid creates its plugins, it has not created all the things
  7380. // it needs to create its view correctly.
  7381. // Process the view and init the RowBody Feature as soon as the view is created.
  7382. {
  7383. grid.on({
  7384. viewcreated: initFeature,
  7385. single: true
  7386. });
  7387. }
  7388. },
  7389. /**
  7390. * Toggle between the preview being expanded/hidden on all rows
  7391. * @param {Boolean} expanded Pass true to expand the record and false to not show the preview.
  7392. */
  7393. toggleExpanded: function(expanded) {
  7394. var grid = this.getCmp(),
  7395. view = grid && grid.getView(),
  7396. bufferedRenderer = view.bufferedRenderer,
  7397. scrollManager = view.scrollManager;
  7398. if (grid && view && expanded !== view.previewExpanded) {
  7399. this.previewExpanded = view.previewExpanded = !!expanded;
  7400. view.refreshView();
  7401. // If we are using the touch scroller, ensure that the scroller knows about
  7402. // the correct scrollable range
  7403. if (scrollManager) {
  7404. if (bufferedRenderer) {
  7405. bufferedRenderer.stretchView(view, bufferedRenderer.getScrollHeight(true));
  7406. } else {
  7407. scrollManager.refresh(true);
  7408. }
  7409. }
  7410. }
  7411. }
  7412. });
  7413. /**
  7414. * Plugin for displaying a progressbar inside of a paging toolbar
  7415. * instead of plain text.
  7416. */
  7417. Ext.define('Ext.ux.ProgressBarPager', {
  7418. alias: 'plugin.ux-progressbarpager',
  7419. requires: [
  7420. 'Ext.ProgressBar'
  7421. ],
  7422. /**
  7423. * @cfg {Number} width
  7424. * <p>The default progress bar width. Default is 225.</p>
  7425. */
  7426. width: 225,
  7427. /**
  7428. * @cfg {String} defaultText
  7429. * <p>The text to display while the store is loading. Default is 'Loading...'</p>
  7430. */
  7431. defaultText: 'Loading...',
  7432. /**
  7433. * @cfg {Object} defaultAnimCfg
  7434. * <p>A {@link Ext.fx.Anim Ext.fx.Anim} configuration object.</p>
  7435. */
  7436. defaultAnimCfg: {
  7437. duration: 1000,
  7438. easing: 'bounceOut'
  7439. },
  7440. /**
  7441. * Creates new ProgressBarPager.
  7442. * @param {Object} config Configuration options
  7443. */
  7444. constructor: function(config) {
  7445. if (config) {
  7446. Ext.apply(this, config);
  7447. }
  7448. },
  7449. init: function(parent) {
  7450. var displayItem;
  7451. if (parent.displayInfo) {
  7452. this.parent = parent;
  7453. displayItem = parent.child("#displayItem");
  7454. if (displayItem) {
  7455. parent.remove(displayItem, true);
  7456. }
  7457. this.progressBar = Ext.create('Ext.ProgressBar', {
  7458. text: this.defaultText,
  7459. width: this.width,
  7460. animate: this.defaultAnimCfg,
  7461. style: {
  7462. cursor: 'pointer'
  7463. },
  7464. listeners: {
  7465. el: {
  7466. scope: this,
  7467. click: this.handleProgressBarClick
  7468. }
  7469. }
  7470. });
  7471. parent.displayItem = this.progressBar;
  7472. parent.add(parent.displayItem);
  7473. Ext.apply(parent, this.parentOverrides);
  7474. }
  7475. },
  7476. /**
  7477. * This method handles the click for the progress bar
  7478. * @private
  7479. */
  7480. handleProgressBarClick: function(e) {
  7481. var parent = this.parent,
  7482. displayItem = parent.displayItem,
  7483. box = this.progressBar.getBox(),
  7484. xy = e.getXY(),
  7485. position = xy[0] - box.x,
  7486. store = parent.store,
  7487. pageSize = parent.pageSize || store.pageSize,
  7488. pages = Math.ceil(store.getTotalCount() / pageSize),
  7489. newPage = Math.max(Math.ceil(position / (displayItem.width / pages)), 1);
  7490. store.loadPage(newPage);
  7491. },
  7492. /**
  7493. * @private
  7494. */
  7495. parentOverrides: {
  7496. /**
  7497. * This method updates the information via the progress bar.
  7498. * @private
  7499. */
  7500. updateInfo: function() {
  7501. if (this.displayItem) {
  7502. // eslint-disable-next-line vars-on-top
  7503. var count = this.store.getCount(),
  7504. pageData = this.getPageData(),
  7505. message = count === 0 ? this.emptyMsg : Ext.String.format(this.displayMsg, pageData.fromRecord, pageData.toRecord, this.store.getTotalCount()),
  7506. percentage = pageData.pageCount > 0 ? (pageData.currentPage / pageData.pageCount) : 0;
  7507. this.displayItem.updateProgress(percentage, message, this.animate || this.defaultAnimConfig);
  7508. }
  7509. }
  7510. }
  7511. });
  7512. /**
  7513. * @deprecated 4.0.0 Ext.ux.RowExpander has been promoted to the core framework. Use
  7514. * {@link Ext.grid.plugin.RowExpander} instead.
  7515. *
  7516. * Ext.ux.RowExpander is now just an empty stub that extends Ext.grid.plugin.RowExpander
  7517. * for backward compatibility reasons.
  7518. */
  7519. Ext.define('Ext.ux.RowExpander', {
  7520. extend: 'Ext.grid.plugin.RowExpander'
  7521. });
  7522. /**
  7523. * Plugin for PagingToolbar which replaces the textfield input with a slider
  7524. */
  7525. Ext.define('Ext.ux.SlidingPager', {
  7526. alias: 'plugin.ux-slidingpager',
  7527. requires: [
  7528. 'Ext.slider.Single',
  7529. 'Ext.slider.Tip'
  7530. ],
  7531. /**
  7532. * Creates new SlidingPager.
  7533. * @param {Object} config Configuration options
  7534. */
  7535. constructor: function(config) {
  7536. if (config) {
  7537. Ext.apply(this, config);
  7538. }
  7539. },
  7540. init: function(pbar) {
  7541. var idx = pbar.items.indexOf(pbar.child("#inputItem")),
  7542. slider;
  7543. Ext.each(pbar.items.getRange(idx - 2, idx + 2), function(c) {
  7544. c.hide();
  7545. });
  7546. slider = Ext.create('Ext.slider.Single', {
  7547. width: 114,
  7548. minValue: 1,
  7549. maxValue: 1,
  7550. hideLabel: true,
  7551. tipText: function(thumb) {
  7552. return Ext.String.format('Page <b>{0}</b> of <b>{1}</b>', thumb.value, thumb.slider.maxValue);
  7553. },
  7554. listeners: {
  7555. changecomplete: function(s, v) {
  7556. pbar.store.loadPage(v);
  7557. }
  7558. }
  7559. });
  7560. pbar.insert(idx + 1, slider);
  7561. pbar.on({
  7562. change: function(pb, data) {
  7563. slider.setMaxValue(data.pageCount);
  7564. slider.setValue(data.currentPage);
  7565. }
  7566. });
  7567. }
  7568. });
  7569. /**
  7570. * UX used to provide a spotlight around a specified component/element.
  7571. */
  7572. Ext.define('Ext.ux.Spotlight', {
  7573. /**
  7574. * @private
  7575. * The baseCls for the spotlight elements
  7576. */
  7577. baseCls: 'x-spotlight',
  7578. /**
  7579. * @cfg animate {Boolean} True to animate the spotlight change
  7580. * (defaults to true)
  7581. */
  7582. animate: true,
  7583. /**
  7584. * @cfg duration {Integer} The duration of the animation, in milliseconds
  7585. * (defaults to 250)
  7586. */
  7587. duration: 250,
  7588. /**
  7589. * @cfg easing {String} The type of easing for the spotlight animatation
  7590. * (defaults to null)
  7591. */
  7592. easing: null,
  7593. /**
  7594. * @private
  7595. * True if the spotlight is active on the element
  7596. */
  7597. active: false,
  7598. constructor: function(config) {
  7599. Ext.apply(this, config);
  7600. },
  7601. /**
  7602. * Create all the elements for the spotlight
  7603. */
  7604. createElements: function() {
  7605. var me = this,
  7606. baseCls = me.baseCls,
  7607. body = Ext.getBody();
  7608. me.right = body.createChild({
  7609. cls: baseCls
  7610. });
  7611. me.left = body.createChild({
  7612. cls: baseCls
  7613. });
  7614. me.top = body.createChild({
  7615. cls: baseCls
  7616. });
  7617. me.bottom = body.createChild({
  7618. cls: baseCls
  7619. });
  7620. me.all = Ext.create('Ext.CompositeElement', [
  7621. me.right,
  7622. me.left,
  7623. me.top,
  7624. me.bottom
  7625. ]);
  7626. },
  7627. /**
  7628. * Show the spotlight
  7629. */
  7630. show: function(el, callback, scope) {
  7631. var me = this;
  7632. // get the target element
  7633. me.el = Ext.get(el);
  7634. // create the elements if they don't already exist
  7635. if (!me.right) {
  7636. me.createElements();
  7637. }
  7638. if (!me.active) {
  7639. // if the spotlight is not active, show it
  7640. me.all.setDisplayed('');
  7641. me.active = true;
  7642. Ext.on('resize', me.syncSize, me);
  7643. me.applyBounds(me.animate, false);
  7644. } else {
  7645. // if the spotlight is currently active, just move it
  7646. me.applyBounds(false, false);
  7647. }
  7648. },
  7649. /**
  7650. * Hide the spotlight
  7651. */
  7652. hide: function(callback, scope) {
  7653. var me = this;
  7654. Ext.un('resize', me.syncSize, me);
  7655. me.applyBounds(me.animate, true);
  7656. },
  7657. /**
  7658. * Resizes the spotlight with the window size.
  7659. */
  7660. syncSize: function() {
  7661. this.applyBounds(false, false);
  7662. },
  7663. /**
  7664. * Resizes the spotlight depending on the arguments
  7665. * @param {Boolean} animate True to animate the changing of the bounds
  7666. * @param {Boolean} reverse True to reverse the animation
  7667. */
  7668. applyBounds: function(animate, reverse) {
  7669. var me = this,
  7670. box = me.el.getBox(),
  7671. // get the current view width and height
  7672. viewWidth = Ext.Element.getViewportWidth(),
  7673. viewHeight = Ext.Element.getViewportHeight(),
  7674. from, to, clone;
  7675. // where the element should start (if animation)
  7676. from = {
  7677. right: {
  7678. x: box.right,
  7679. y: viewHeight,
  7680. width: (viewWidth - box.right),
  7681. height: 0
  7682. },
  7683. left: {
  7684. x: 0,
  7685. y: 0,
  7686. width: box.x,
  7687. height: 0
  7688. },
  7689. top: {
  7690. x: viewWidth,
  7691. y: 0,
  7692. width: 0,
  7693. height: box.y
  7694. },
  7695. bottom: {
  7696. x: 0,
  7697. y: (box.y + box.height),
  7698. width: 0,
  7699. height: (viewHeight - (box.y + box.height)) + 'px'
  7700. }
  7701. };
  7702. // where the element needs to finish
  7703. to = {
  7704. right: {
  7705. x: box.right,
  7706. y: box.y,
  7707. width: (viewWidth - box.right) + 'px',
  7708. height: (viewHeight - box.y) + 'px'
  7709. },
  7710. left: {
  7711. x: 0,
  7712. y: 0,
  7713. width: box.x + 'px',
  7714. height: (box.y + box.height) + 'px'
  7715. },
  7716. top: {
  7717. x: box.x,
  7718. y: 0,
  7719. width: (viewWidth - box.x) + 'px',
  7720. height: box.y + 'px'
  7721. },
  7722. bottom: {
  7723. x: 0,
  7724. y: (box.y + box.height),
  7725. width: (box.x + box.width) + 'px',
  7726. height: (viewHeight - (box.y + box.height)) + 'px'
  7727. }
  7728. };
  7729. // reverse the objects
  7730. if (reverse) {
  7731. clone = Ext.clone(from);
  7732. from = to;
  7733. to = clone;
  7734. }
  7735. if (animate) {
  7736. Ext.Array.forEach([
  7737. 'right',
  7738. 'left',
  7739. 'top',
  7740. 'bottom'
  7741. ], function(side) {
  7742. me[side].setBox(from[side]);
  7743. me[side].animate({
  7744. duration: me.duration,
  7745. easing: me.easing,
  7746. to: to[side]
  7747. });
  7748. }, this);
  7749. } else {
  7750. Ext.Array.forEach([
  7751. 'right',
  7752. 'left',
  7753. 'top',
  7754. 'bottom'
  7755. ], function(side) {
  7756. me[side].setBox(Ext.apply(from[side], to[side]));
  7757. me[side].repaint();
  7758. }, this);
  7759. }
  7760. },
  7761. /**
  7762. * Removes all the elements for the spotlight
  7763. */
  7764. destroy: function() {
  7765. var me = this;
  7766. Ext.destroy(me.right, me.left, me.top, me.bottom);
  7767. delete me.el;
  7768. delete me.all;
  7769. me.callParent();
  7770. }
  7771. });
  7772. /**
  7773. * Plugin for adding a close context menu to tabs. Note that the menu respects
  7774. * the closable configuration on the tab. As such, commands like remove others
  7775. * and remove all will not remove items that are not closable.
  7776. */
  7777. Ext.define('Ext.ux.TabCloseMenu', {
  7778. extend: 'Ext.plugin.Abstract',
  7779. alias: 'plugin.tabclosemenu',
  7780. mixins: {
  7781. observable: 'Ext.util.Observable'
  7782. },
  7783. /**
  7784. * @cfg {String} closeTabText
  7785. * The text for closing the current tab.
  7786. */
  7787. closeTabText: 'Close Tab',
  7788. /**
  7789. * @cfg {Boolean} showCloseOthers
  7790. * Indicates whether to show the 'Close Others' option.
  7791. */
  7792. showCloseOthers: true,
  7793. /**
  7794. * @cfg {String} closeOtherTabsText
  7795. * The text for closing all tabs except the current one.
  7796. */
  7797. closeOtherTabsText: 'Close Other Tabs',
  7798. /**
  7799. * @cfg {Boolean} showCloseAll
  7800. * Indicates whether to show the 'Close All' option.
  7801. */
  7802. showCloseAll: true,
  7803. /**
  7804. * @cfg {String} closeAllTabsText
  7805. * The text for closing all tabs.
  7806. */
  7807. closeAllTabsText: 'Close All Tabs',
  7808. /**
  7809. * @cfg {Array} extraItemsHead
  7810. * An array of additional context menu items to add to the front of the context menu.
  7811. */
  7812. extraItemsHead: null,
  7813. /**
  7814. * @cfg {Array} extraItemsTail
  7815. * An array of additional context menu items to add to the end of the context menu.
  7816. */
  7817. extraItemsTail: null,
  7818. // public
  7819. constructor: function(config) {
  7820. this.callParent([
  7821. config
  7822. ]);
  7823. this.mixins.observable.constructor.call(this, config);
  7824. },
  7825. init: function(tabpanel) {
  7826. this.tabPanel = tabpanel;
  7827. this.tabBar = tabpanel.down("tabbar");
  7828. this.mon(this.tabPanel, {
  7829. scope: this,
  7830. afterlayout: this.onAfterLayout,
  7831. single: true
  7832. });
  7833. },
  7834. onAfterLayout: function() {
  7835. this.mon(this.tabBar.el, {
  7836. scope: this,
  7837. contextmenu: this.onContextMenu,
  7838. delegate: '.x-tab'
  7839. });
  7840. },
  7841. destroy: function() {
  7842. Ext.destroy(this.menu);
  7843. this.callParent();
  7844. },
  7845. /**
  7846. * @private
  7847. */
  7848. onContextMenu: function(event, target) {
  7849. var me = this,
  7850. menu = me.createMenu(),
  7851. disableAll = true,
  7852. disableOthers = true,
  7853. tab = me.tabBar.getChildByElement(target),
  7854. index = me.tabBar.items.indexOf(tab);
  7855. me.item = me.tabPanel.getComponent(index);
  7856. menu.child('#close').setDisabled(!me.item.closable);
  7857. if (me.showCloseAll || me.showCloseOthers) {
  7858. me.tabPanel.items.each(function(item) {
  7859. if (item.closable) {
  7860. disableAll = false;
  7861. if (item !== me.item) {
  7862. disableOthers = false;
  7863. return false;
  7864. }
  7865. }
  7866. return true;
  7867. });
  7868. if (me.showCloseAll) {
  7869. menu.child('#closeAll').setDisabled(disableAll);
  7870. }
  7871. if (me.showCloseOthers) {
  7872. menu.child('#closeOthers').setDisabled(disableOthers);
  7873. }
  7874. }
  7875. event.preventDefault();
  7876. me.fireEvent('beforemenu', menu, me.item, me);
  7877. menu.showAt(event.getXY());
  7878. },
  7879. createMenu: function() {
  7880. var me = this,
  7881. items;
  7882. if (!me.menu) {
  7883. items = [
  7884. {
  7885. itemId: 'close',
  7886. text: me.closeTabText,
  7887. scope: me,
  7888. handler: me.onClose
  7889. }
  7890. ];
  7891. if (me.showCloseAll || me.showCloseOthers) {
  7892. items.push('-');
  7893. }
  7894. if (me.showCloseOthers) {
  7895. items.push({
  7896. itemId: 'closeOthers',
  7897. text: me.closeOtherTabsText,
  7898. scope: me,
  7899. handler: me.onCloseOthers
  7900. });
  7901. }
  7902. if (me.showCloseAll) {
  7903. items.push({
  7904. itemId: 'closeAll',
  7905. text: me.closeAllTabsText,
  7906. scope: me,
  7907. handler: me.onCloseAll
  7908. });
  7909. }
  7910. if (me.extraItemsHead) {
  7911. items = me.extraItemsHead.concat(items);
  7912. }
  7913. if (me.extraItemsTail) {
  7914. items = items.concat(me.extraItemsTail);
  7915. }
  7916. me.menu = Ext.create('Ext.menu.Menu', {
  7917. items: items,
  7918. listeners: {
  7919. hide: me.onHideMenu,
  7920. scope: me
  7921. }
  7922. });
  7923. }
  7924. return me.menu;
  7925. },
  7926. onHideMenu: function() {
  7927. var me = this;
  7928. me.fireEvent('aftermenu', me.menu, me);
  7929. },
  7930. onClose: function() {
  7931. this.tabPanel.remove(this.item);
  7932. },
  7933. onCloseOthers: function() {
  7934. this.doClose(true);
  7935. },
  7936. onCloseAll: function() {
  7937. this.doClose(false);
  7938. },
  7939. doClose: function(excludeActive) {
  7940. var items = [];
  7941. this.tabPanel.items.each(function(item) {
  7942. if (item.closable) {
  7943. if (!excludeActive || item !== this.item) {
  7944. items.push(item);
  7945. }
  7946. }
  7947. }, this);
  7948. Ext.suspendLayouts();
  7949. Ext.Array.forEach(items, function(item) {
  7950. this.tabPanel.remove(item);
  7951. }, this);
  7952. Ext.resumeLayouts(true);
  7953. }
  7954. });
  7955. /**
  7956. * This plugin allow you to reorder tabs of a TabPanel.
  7957. */
  7958. Ext.define('Ext.ux.TabReorderer', {
  7959. extend: 'Ext.ux.BoxReorderer',
  7960. alias: 'plugin.tabreorderer',
  7961. itemSelector: '.' + Ext.baseCSSPrefix + 'tab',
  7962. init: function(tabPanel) {
  7963. var me = this;
  7964. me.callParent([
  7965. tabPanel.getTabBar()
  7966. ]);
  7967. // Ensure reorderable property is copied into dynamically added tabs
  7968. tabPanel.onAdd = Ext.Function.createSequence(tabPanel.onAdd, me.onAdd);
  7969. },
  7970. onBoxReady: function() {
  7971. var tabs, tab, len,
  7972. i = 0;
  7973. this.callParent(arguments);
  7974. // Copy reorderable property from card into tab
  7975. for (tabs = this.container.items.items , len = tabs.length; i < len; i++) {
  7976. tab = tabs[i];
  7977. if (tab.card) {
  7978. tab.reorderable = tab.card.reorderable;
  7979. }
  7980. }
  7981. },
  7982. onAdd: function(card, index) {
  7983. card.tab.reorderable = card.reorderable;
  7984. },
  7985. afterBoxReflow: function() {
  7986. var me = this;
  7987. // Cannot use callParent, this is not called in the scope of this plugin,
  7988. // but that of its Ext.dd.DD object
  7989. Ext.ux.BoxReorderer.prototype.afterBoxReflow.apply(me, arguments);
  7990. // Move the associated card to match the tab order
  7991. if (me.dragCmp) {
  7992. me.container.tabPanel.setActiveTab(me.dragCmp.card);
  7993. me.container.tabPanel.move(me.dragCmp.card, me.curIndex);
  7994. }
  7995. }
  7996. });
  7997. Ext.ns('Ext.ux');
  7998. /**
  7999. * Plugin for adding a tab menu to a TabBar is the Tabs overflow.
  8000. */
  8001. Ext.define('Ext.ux.TabScrollerMenu', {
  8002. alias: 'plugin.tabscrollermenu',
  8003. requires: [
  8004. 'Ext.menu.Menu'
  8005. ],
  8006. /**
  8007. * @cfg {Number} pageSize How many items to allow per submenu.
  8008. */
  8009. pageSize: 10,
  8010. /**
  8011. * @cfg {Number} maxText How long should the title of each {@link Ext.menu.Item} be.
  8012. */
  8013. maxText: 15,
  8014. /**
  8015. * @cfg {String} menuPrefixText Text to prefix the submenus.
  8016. */
  8017. menuPrefixText: 'Items',
  8018. /**
  8019. * Creates new TabScrollerMenu.
  8020. * @param {Object} config Configuration options
  8021. */
  8022. constructor: function(config) {
  8023. Ext.apply(this, config);
  8024. },
  8025. /**
  8026. * @private
  8027. */
  8028. init: function(tabPanel) {
  8029. var me = this;
  8030. me.tabPanel = tabPanel;
  8031. tabPanel.on({
  8032. render: function() {
  8033. me.tabBar = tabPanel.tabBar;
  8034. me.layout = me.tabBar.layout;
  8035. me.layout.overflowHandler.handleOverflow = me.showButton.bind(me);
  8036. me.layout.overflowHandler.clearOverflow = Ext.Function.createSequence(me.layout.overflowHandler.clearOverflow, me.hideButton, me);
  8037. },
  8038. destroy: me.destroy,
  8039. scope: me,
  8040. single: true
  8041. });
  8042. },
  8043. showButton: function() {
  8044. var me = this,
  8045. result, button;
  8046. result = Ext.getClass(me.layout.overflowHandler).prototype.handleOverflow.apply(me.layout.overflowHandler, arguments);
  8047. button = me.menuButton;
  8048. if (me.tabPanel.items.getCount() > 1) {
  8049. if (!button) {
  8050. button = me.menuButton = me.tabBar.body.createChild({
  8051. cls: Ext.baseCSSPrefix + 'tab-tabmenu-right'
  8052. }, me.tabBar.body.child('.' + Ext.baseCSSPrefix + 'box-scroller-right'));
  8053. button.addClsOnOver(Ext.baseCSSPrefix + 'tab-tabmenu-over');
  8054. button.on('click', me.showTabsMenu, me);
  8055. }
  8056. button.setVisibilityMode(Ext.dom.Element.DISPLAY);
  8057. button.show();
  8058. result.reservedSpace += button.getWidth();
  8059. } else {
  8060. me.hideButton();
  8061. }
  8062. return result;
  8063. },
  8064. hideButton: function() {
  8065. var me = this;
  8066. if (me.menuButton) {
  8067. me.menuButton.hide();
  8068. }
  8069. },
  8070. /**
  8071. * Returns an the current page size (this.pageSize);
  8072. * @return {Number} this.pageSize The current page size.
  8073. */
  8074. getPageSize: function() {
  8075. return this.pageSize;
  8076. },
  8077. /**
  8078. * Sets the number of menu items per submenu "page size".
  8079. * @param {Number} pageSize The page size
  8080. */
  8081. setPageSize: function(pageSize) {
  8082. this.pageSize = pageSize;
  8083. },
  8084. /**
  8085. * Returns the current maxText length;
  8086. * @return {Number} this.maxText The current max text length.
  8087. */
  8088. getMaxText: function() {
  8089. return this.maxText;
  8090. },
  8091. /**
  8092. * Sets the maximum text size for each menu item.
  8093. * @param {Number} t The max text per each menu item.
  8094. */
  8095. setMaxText: function(t) {
  8096. this.maxText = t;
  8097. },
  8098. /**
  8099. * Returns the current menu prefix text String.;
  8100. * @return {String} this.menuPrefixText The current menu prefix text.
  8101. */
  8102. getMenuPrefixText: function() {
  8103. return this.menuPrefixText;
  8104. },
  8105. /**
  8106. * Sets the menu prefix text String.
  8107. * @param {String} t The menu prefix text.
  8108. */
  8109. setMenuPrefixText: function(t) {
  8110. this.menuPrefixText = t;
  8111. },
  8112. showTabsMenu: function(e) {
  8113. var me = this,
  8114. target, xy;
  8115. if (me.tabsMenu) {
  8116. me.tabsMenu.removeAll();
  8117. } else {
  8118. me.tabsMenu = new Ext.menu.Menu();
  8119. }
  8120. me.generateTabMenuItems();
  8121. target = Ext.get(e.getTarget());
  8122. xy = target.getXY();
  8123. // Y param + 24 pixels
  8124. xy[1] += 24;
  8125. me.tabsMenu.showAt(xy);
  8126. },
  8127. /**
  8128. * @private
  8129. */
  8130. generateTabMenuItems: function() {
  8131. var me = this,
  8132. tabPanel = me.tabPanel,
  8133. curActive = tabPanel.getActiveTab(),
  8134. allItems = tabPanel.items.getRange(),
  8135. pageSize = me.getPageSize(),
  8136. tabsMenu = me.tabsMenu,
  8137. totalItems, numSubMenus, remainder, i, curPage, menuItems, x, item, start, index;
  8138. tabsMenu.suspendLayouts();
  8139. allItems = Ext.Array.filter(allItems, function(item) {
  8140. if (item.id === curActive.id) {
  8141. return false;
  8142. }
  8143. return item.hidden ? !!item.hiddenByLayout : true;
  8144. });
  8145. totalItems = allItems.length;
  8146. numSubMenus = Math.floor(totalItems / pageSize);
  8147. remainder = totalItems % pageSize;
  8148. if (totalItems > pageSize) {
  8149. // Loop through all of the items and create submenus in chunks of 10
  8150. for (i = 0; i < numSubMenus; i++) {
  8151. curPage = (i + 1) * pageSize;
  8152. menuItems = [];
  8153. for (x = 0; x < pageSize; x++) {
  8154. index = x + curPage - pageSize;
  8155. item = allItems[index];
  8156. menuItems.push(me.autoGenMenuItem(item));
  8157. }
  8158. tabsMenu.add({
  8159. text: me.getMenuPrefixText() + ' ' + (curPage - pageSize + 1) + ' - ' + curPage,
  8160. menu: menuItems
  8161. });
  8162. }
  8163. // remaining items
  8164. if (remainder > 0) {
  8165. start = numSubMenus * pageSize;
  8166. menuItems = [];
  8167. for (i = start; i < totalItems; i++) {
  8168. item = allItems[i];
  8169. menuItems.push(me.autoGenMenuItem(item));
  8170. }
  8171. me.tabsMenu.add({
  8172. text: me.menuPrefixText + ' ' + (start + 1) + ' - ' + (start + menuItems.length),
  8173. menu: menuItems
  8174. });
  8175. }
  8176. } else {
  8177. for (i = 0; i < totalItems; ++i) {
  8178. tabsMenu.add(me.autoGenMenuItem(allItems[i]));
  8179. }
  8180. }
  8181. tabsMenu.resumeLayouts(true);
  8182. },
  8183. /**
  8184. * @private
  8185. */
  8186. autoGenMenuItem: function(item) {
  8187. var maxText = this.getMaxText(),
  8188. text = Ext.util.Format.ellipsis(item.title, maxText);
  8189. return {
  8190. text: text,
  8191. handler: this.showTabFromMenu,
  8192. scope: this,
  8193. disabled: item.disabled,
  8194. tabToShow: item,
  8195. iconCls: item.iconCls
  8196. };
  8197. },
  8198. /**
  8199. * @private
  8200. */
  8201. showTabFromMenu: function(menuItem) {
  8202. this.tabPanel.setActiveTab(menuItem.tabToShow);
  8203. },
  8204. destroy: function() {
  8205. Ext.destroy(this.tabsMenu, this.menuButton);
  8206. this.callParent();
  8207. }
  8208. });
  8209. /**
  8210. * Plugin which allows items to be dropped onto a toolbar and be turned into new Toolbar items.
  8211. * To use the plugin, you just need to provide a createItem implementation that takes the drop
  8212. * data as an argument and returns an object that can be placed onto the toolbar. Example:
  8213. * <pre>
  8214. * Ext.create('Ext.ux.ToolbarDroppable', {
  8215. * createItem: function(data) {
  8216. * return Ext.create('Ext.Button', {text: data.text});
  8217. * }
  8218. * });
  8219. * </pre>
  8220. * The afterLayout function can also be overridden, and is called after a new item has been
  8221. * created and inserted into the Toolbar. Use this for any logic that needs to be run after
  8222. * the item has been created.
  8223. */
  8224. Ext.define('Ext.ux.ToolbarDroppable', {
  8225. /**
  8226. * Creates new ToolbarDroppable.
  8227. * @param {Object} config Config options.
  8228. */
  8229. constructor: function(config) {
  8230. Ext.apply(this, config);
  8231. },
  8232. /**
  8233. * Initializes the plugin and saves a reference to the toolbar
  8234. * @param {Ext.toolbar.Toolbar} toolbar The toolbar instance
  8235. */
  8236. init: function(toolbar) {
  8237. /**
  8238. * @property toolbar
  8239. * @type Ext.toolbar.Toolbar
  8240. * The toolbar instance that this plugin is tied to
  8241. */
  8242. this.toolbar = toolbar;
  8243. this.toolbar.on({
  8244. scope: this,
  8245. render: this.createDropTarget
  8246. });
  8247. },
  8248. /**
  8249. * Creates a drop target on the toolbar
  8250. */
  8251. createDropTarget: function() {
  8252. /**
  8253. * @property dropTarget
  8254. * @type Ext.dd.DropTarget
  8255. * The drop target attached to the toolbar instance
  8256. */
  8257. this.dropTarget = Ext.create('Ext.dd.DropTarget', this.toolbar.getEl(), {
  8258. notifyOver: this.notifyOver.bind(this),
  8259. notifyDrop: this.notifyDrop.bind(this)
  8260. });
  8261. },
  8262. /**
  8263. * Adds the given DD Group to the drop target
  8264. * @param {String} ddGroup The DD Group
  8265. */
  8266. addDDGroup: function(ddGroup) {
  8267. this.dropTarget.addToGroup(ddGroup);
  8268. },
  8269. /**
  8270. * Calculates the location on the toolbar to create the new sorter button based on the XY of the
  8271. * drag event
  8272. * @param {Ext.event.Event} e The event object
  8273. * @return {Number} The index at which to insert the new button
  8274. */
  8275. calculateEntryIndex: function(e) {
  8276. var entryIndex = 0,
  8277. toolbar = this.toolbar,
  8278. items = toolbar.items.items,
  8279. count = items.length,
  8280. xHover = e.getXY()[0],
  8281. index = 0,
  8282. el, xTotal, width, midpoint;
  8283. for (; index < count; index++) {
  8284. el = items[index].getEl();
  8285. xTotal = el.getXY()[0];
  8286. width = el.getWidth();
  8287. midpoint = xTotal + width / 2;
  8288. if (xHover < midpoint) {
  8289. entryIndex = index;
  8290. break;
  8291. } else {
  8292. entryIndex = index + 1;
  8293. }
  8294. }
  8295. return entryIndex;
  8296. },
  8297. /**
  8298. * Returns true if the drop is allowed on the drop target. This function can be overridden
  8299. * and defaults to simply return true
  8300. * @param {Object} data Arbitrary data from the drag source
  8301. * @return {Boolean} True if the drop is allowed
  8302. */
  8303. canDrop: function(data) {
  8304. return true;
  8305. },
  8306. /**
  8307. * Custom notifyOver method which will be used in the plugin's internal DropTarget
  8308. * @return {String} The CSS class to add
  8309. */
  8310. notifyOver: function(dragSource, event, data) {
  8311. return this.canDrop.apply(this, arguments) ? this.dropTarget.dropAllowed : this.dropTarget.dropNotAllowed;
  8312. },
  8313. /**
  8314. * Called when the drop has been made. Creates the new toolbar item, places it
  8315. * at the correct location and calls the afterLayout callback.
  8316. */
  8317. notifyDrop: function(dragSource, event, data) {
  8318. var canAdd = this.canDrop(dragSource, event, data),
  8319. tbar = this.toolbar,
  8320. entryIndex;
  8321. if (canAdd) {
  8322. entryIndex = this.calculateEntryIndex(event);
  8323. tbar.insert(entryIndex, this.createItem(data));
  8324. this.afterLayout();
  8325. }
  8326. return canAdd;
  8327. },
  8328. /**
  8329. * Creates the new toolbar item based on drop data. This method must be implemented
  8330. * by the plugin instance
  8331. * @param {Object} data Arbitrary data from the drop
  8332. * @return {Mixed} An item that can be added to a toolbar
  8333. */
  8334. createItem: function(data) {
  8335. //<debug>
  8336. Ext.raise("The createItem method must be implemented in the ToolbarDroppable plugin");
  8337. },
  8338. //</debug>
  8339. /**
  8340. * @method
  8341. * Called after a new button has been created and added to the toolbar. Add any required
  8342. * cleanup logic here
  8343. */
  8344. afterLayout: Ext.emptyFn
  8345. });
  8346. /**
  8347. * A Picker field that contains a tree panel on its popup, enabling selection of tree nodes.
  8348. */
  8349. Ext.define('Ext.ux.TreePicker', {
  8350. extend: 'Ext.form.field.Picker',
  8351. xtype: 'treepicker',
  8352. uses: [
  8353. 'Ext.tree.Panel'
  8354. ],
  8355. triggerCls: Ext.baseCSSPrefix + 'form-arrow-trigger',
  8356. config: {
  8357. /**
  8358. * @cfg {Ext.data.TreeStore} store
  8359. * A tree store that the tree picker will be bound to
  8360. */
  8361. store: null,
  8362. /**
  8363. * @cfg {String} displayField
  8364. * The field inside the model that will be used as the node's text.
  8365. * Defaults to the default value of {@link Ext.tree.Panel}'s `displayField` configuration.
  8366. */
  8367. displayField: null,
  8368. /**
  8369. * @cfg {Array} columns
  8370. * An optional array of columns for multi-column trees
  8371. */
  8372. columns: null,
  8373. /**
  8374. * @cfg {Boolean} selectOnTab
  8375. * Whether the Tab key should select the currently highlighted item. Defaults to `true`.
  8376. */
  8377. selectOnTab: true,
  8378. /**
  8379. * @cfg {Number} maxPickerHeight
  8380. * The maximum height of the tree dropdown. Defaults to 300.
  8381. */
  8382. maxPickerHeight: 300,
  8383. /**
  8384. * @cfg {Number} minPickerHeight
  8385. * The minimum height of the tree dropdown. Defaults to 100.
  8386. */
  8387. minPickerHeight: 100
  8388. },
  8389. editable: false,
  8390. /**
  8391. * @event select
  8392. * Fires when a tree node is selected
  8393. * @param {Ext.ux.TreePicker} picker This tree picker
  8394. * @param {Ext.data.Model} record The selected record
  8395. */
  8396. initComponent: function() {
  8397. var me = this;
  8398. me.callParent(arguments);
  8399. me.mon(me.store, {
  8400. scope: me,
  8401. load: me.onLoad,
  8402. update: me.onUpdate
  8403. });
  8404. },
  8405. /**
  8406. * Creates and returns the tree panel to be used as this field's picker.
  8407. */
  8408. createPicker: function() {
  8409. var me = this,
  8410. picker = new Ext.tree.Panel({
  8411. baseCls: Ext.baseCSSPrefix + 'boundlist',
  8412. shrinkWrapDock: 2,
  8413. store: me.store,
  8414. floating: true,
  8415. displayField: me.displayField,
  8416. columns: me.columns,
  8417. minHeight: me.minPickerHeight,
  8418. maxHeight: me.maxPickerHeight,
  8419. manageHeight: false,
  8420. shadow: false,
  8421. listeners: {
  8422. scope: me,
  8423. itemclick: me.onItemClick,
  8424. itemkeydown: me.onPickerKeyDown
  8425. }
  8426. }),
  8427. view = picker.getView();
  8428. if (Ext.isIE9 && Ext.isStrict) {
  8429. // In IE9 strict mode, the tree view grows by the height of the horizontal scroll bar
  8430. // when the items are highlighted or unhighlighted.
  8431. // Also when items are collapsed or expanded the height of the view is off.
  8432. // Forcing a repaint fixes the problem.
  8433. view.on({
  8434. scope: me,
  8435. highlightitem: me.repaintPickerView,
  8436. unhighlightitem: me.repaintPickerView,
  8437. afteritemexpand: me.repaintPickerView,
  8438. afteritemcollapse: me.repaintPickerView
  8439. });
  8440. }
  8441. return picker;
  8442. },
  8443. /**
  8444. * repaints the tree view
  8445. */
  8446. repaintPickerView: function() {
  8447. var style = this.picker.getView().getEl().dom.style;
  8448. // can't use Element.repaint because it contains a setTimeout, which results
  8449. // in a flicker effect
  8450. style.display = style.display;
  8451. },
  8452. // eslint-disable-line no-self-assign
  8453. /**
  8454. * Handles a click even on a tree node
  8455. * @private
  8456. * @param {Ext.tree.View} view
  8457. * @param {Ext.data.Model} record
  8458. * @param {HTMLElement} node
  8459. * @param {Number} rowIndex
  8460. * @param {Ext.event.Event} e
  8461. */
  8462. onItemClick: function(view, record, node, rowIndex, e) {
  8463. this.selectItem(record);
  8464. },
  8465. /**
  8466. * Handles a keypress event on the picker element
  8467. * @private
  8468. * @param {Ext.tree.View} treeView
  8469. * @param {Ext.data.Model} record
  8470. * @param {HTMLElement} item
  8471. * @param {Number} index
  8472. * @param {Ext.event.Event} e
  8473. */
  8474. onPickerKeyDown: function(treeView, record, item, index, e) {
  8475. var key = e.getKey();
  8476. if (key === e.ENTER || (key === e.TAB && this.selectOnTab)) {
  8477. this.selectItem(record);
  8478. }
  8479. },
  8480. /**
  8481. * Changes the selection to a given record and closes the picker
  8482. * @private
  8483. * @param {Ext.data.Model} record
  8484. */
  8485. selectItem: function(record) {
  8486. var me = this;
  8487. me.setValue(record.getId());
  8488. me.fireEvent('select', me, record);
  8489. me.collapse();
  8490. },
  8491. /**
  8492. * Runs when the picker is expanded. Selects the appropriate tree node based on the value
  8493. * of the input element, and focuses the picker so that keyboard navigation will work.
  8494. * @private
  8495. */
  8496. onExpand: function() {
  8497. var picker = this.picker,
  8498. store = picker.store,
  8499. value = this.value,
  8500. node;
  8501. if (value) {
  8502. node = store.getNodeById(value);
  8503. }
  8504. if (!node) {
  8505. node = store.getRoot();
  8506. }
  8507. picker.ensureVisible(node, {
  8508. select: true,
  8509. focus: true
  8510. });
  8511. },
  8512. /**
  8513. * Sets the specified value into the field
  8514. * @param {Mixed} value
  8515. * @return {Ext.ux.TreePicker} this
  8516. */
  8517. setValue: function(value) {
  8518. var me = this,
  8519. record;
  8520. me.value = value;
  8521. if (me.store.loading) {
  8522. // Called while the Store is loading. Ensure it is processed by the onLoad method.
  8523. return me;
  8524. }
  8525. // try to find a record in the store that matches the value
  8526. record = value ? me.store.getNodeById(value) : me.store.getRoot();
  8527. if (value === undefined) {
  8528. record = me.store.getRoot();
  8529. me.value = record.getId();
  8530. } else {
  8531. record = me.store.getNodeById(value);
  8532. }
  8533. // set the raw value to the record's display field if a record was found
  8534. me.setRawValue(record ? record.get(me.displayField) : '');
  8535. return me;
  8536. },
  8537. getSubmitValue: function() {
  8538. return this.value;
  8539. },
  8540. /**
  8541. * Returns the current data value of the field (the idProperty of the record)
  8542. * @return {Number}
  8543. */
  8544. getValue: function() {
  8545. return this.value;
  8546. },
  8547. /**
  8548. * Handles the store's load event.
  8549. * @private
  8550. */
  8551. onLoad: function() {
  8552. var value = this.value;
  8553. if (value) {
  8554. this.setValue(value);
  8555. }
  8556. },
  8557. onUpdate: function(store, rec, type, modifiedFieldNames) {
  8558. var display = this.displayField;
  8559. if (type === 'edit' && modifiedFieldNames && Ext.Array.contains(modifiedFieldNames, display) && this.value === rec.getId()) {
  8560. this.setRawValue(rec.get(display));
  8561. }
  8562. }
  8563. });
  8564. /**
  8565. * @private
  8566. */
  8567. Ext.define('Ext.ux.colorpick.Selection', {
  8568. mixinId: 'colorselection',
  8569. /* eslint-disable max-len */
  8570. config: {
  8571. /**
  8572. * @cfg {"hex6"/"hex8"/"#hex6"/"#hex8"/"rgb"/"rgba"/"HEX6"/"HEX8"/"#HEX6"/"#HEX8"/"RGB"/"RGBA"} [format=hex6]
  8573. * The color format to for the `value` config. The `value` can be set using any
  8574. * supported format or named color, but the stored value will always be in this
  8575. * format.
  8576. *
  8577. * Supported formats are:
  8578. *
  8579. * - hex6 - For example "ffaa00" (Note: does not preserve transparency).
  8580. * - hex8 - For example "ffaa00ff" - the last 2 digits represent transparency
  8581. * - #hex6 - For example "#ffaa00" (same as "hex6" but with a leading "#").
  8582. * - #hex8 - For example "#ffaa00ff" (same as "hex8" but with a leading "#").
  8583. * - rgb - For example "rgb(255,255,0)" (Note: does not preserve transparency).
  8584. * - rgba - For example "rgba(255,255,0,.25)"
  8585. * - HEX6 - Same as "hex6" but upper case.
  8586. * - HEX8 - Same as "hex8" but upper case.
  8587. * - #HEX6 - Same as "#hex6" but upper case.
  8588. * - #HEX8 - Same as "#hex8" but upper case.
  8589. * - RGB - Same as "rgb" but upper case.
  8590. * - RGBA - Same as "rgba" but upper case.
  8591. */
  8592. format: 'hex6',
  8593. /* eslint-enable max-len */
  8594. /**
  8595. * @cfg {String} [value=FF0000]
  8596. * The initial color to highlight; see {@link #format} for supported formats.
  8597. */
  8598. value: 'FF0000',
  8599. /**
  8600. * @cfg {Object} color
  8601. * This config property is used internally by the UI to maintain the full color.
  8602. * Changes to this config are automatically reflected in `value` and vise-versa.
  8603. * Setting `value` can, however, cause the alpha to be dropped if the new value
  8604. * does not contain an alpha component.
  8605. * @private
  8606. */
  8607. color: null,
  8608. previousColor: null,
  8609. /**
  8610. * @cfg {String} [alphaDecimalFormat=#.##]
  8611. * The format used by {@link Ext.util.Format#number} to format the alpha channel's
  8612. * value.
  8613. * @since 7.0.0
  8614. */
  8615. alphaDecimalFormat: '#.##'
  8616. },
  8617. applyColor: function(color) {
  8618. var c = color;
  8619. if (Ext.isString(c)) {
  8620. c = Ext.ux.colorpick.ColorUtils.parseColor(color, this.getAlphaDecimalFormat());
  8621. }
  8622. return c;
  8623. },
  8624. applyFormat: function(format) {
  8625. var formats = Ext.ux.colorpick.ColorUtils.formats;
  8626. if (!formats.hasOwnProperty(format)) {
  8627. //<debug>
  8628. Ext.raise('The specified format "' + format + '" is invalid.');
  8629. //</debug>
  8630. return;
  8631. }
  8632. return format;
  8633. },
  8634. applyValue: function(color) {
  8635. // Transform whatever incoming color we get to the proper format
  8636. var c = Ext.ux.colorpick.ColorUtils.parseColor(color || '#000000', this.getAlphaDecimalFormat());
  8637. return this.formatColor(c);
  8638. },
  8639. formatColor: function(color) {
  8640. return Ext.ux.colorpick.ColorUtils.formats[this.getFormat()](color);
  8641. },
  8642. updateColor: function(color) {
  8643. var me = this;
  8644. // If the "color" is changed (via internal changes in the UI), update "value" as
  8645. // well. Since these are always tracking each other, we guard against the case
  8646. // where we are being updated *because* "value" is being set.
  8647. if (!me.syncing) {
  8648. me.syncing = true;
  8649. me.setValue(me.formatColor(color));
  8650. me.syncing = false;
  8651. }
  8652. },
  8653. updateValue: function(value, oldValue) {
  8654. var me = this;
  8655. // If the "value" is changed, update "color" as well. Since these are always
  8656. // tracking each other, we guard against the case where we are being updated
  8657. // *because* "color" is being set.
  8658. if (!me.syncing) {
  8659. me.syncing = true;
  8660. me.setColor(value);
  8661. me.syncing = false;
  8662. }
  8663. this.fireEvent('change', me, value, oldValue);
  8664. }
  8665. });
  8666. /**
  8667. * @private
  8668. */
  8669. Ext.define('Ext.ux.colorpick.ColorUtils', function(ColorUtils) {
  8670. var oldIE = Ext.isIE && Ext.ieVersion < 10;
  8671. return {
  8672. singleton: true,
  8673. constructor: function() {
  8674. ColorUtils = this;
  8675. },
  8676. backgroundTpl: oldIE ? 'filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, ' + 'startColorstr=\'#{alpha}{hex}\', endColorstr=\'#{alpha}{hex}\');' : 'background: {rgba};',
  8677. setBackground: oldIE ? function(el, color) {
  8678. var tpl, data, bgStyle;
  8679. if (el) {
  8680. tpl = Ext.XTemplate.getTpl(ColorUtils, 'backgroundTpl');
  8681. data = {
  8682. hex: ColorUtils.rgb2hex(color.r, color.g, color.b),
  8683. alpha: Math.floor(color.a * 255).toString(16)
  8684. };
  8685. bgStyle = tpl.apply(data);
  8686. el.applyStyles(bgStyle);
  8687. }
  8688. } : function(el, color) {
  8689. var tpl, data, bgStyle;
  8690. if (el) {
  8691. tpl = Ext.XTemplate.getTpl(ColorUtils, 'backgroundTpl');
  8692. data = {
  8693. rgba: ColorUtils.getRGBAString(color)
  8694. };
  8695. bgStyle = tpl.apply(data);
  8696. el.applyStyles(bgStyle);
  8697. }
  8698. },
  8699. // parse and format functions under objects that match supported format config
  8700. // values of the color picker; parse() methods receive the supplied color value
  8701. // as a string (i.e "FFAAAA") and return an object form, just like the one
  8702. // ColorPickerModel vm "selectedColor" uses. That same object form is used as a
  8703. // parameter to the format() methods, where the appropriate string form is expected
  8704. // for the return result
  8705. formats: {
  8706. // "RGB(100,100,100)"
  8707. RGB: function(colorO) {
  8708. return ColorUtils.getRGBString(colorO).toUpperCase();
  8709. },
  8710. // "RGBA(100,100,100,0.5)"
  8711. RGBA: function(colorO) {
  8712. return ColorUtils.getRGBAString(colorO).toUpperCase();
  8713. },
  8714. // "FFAA00"
  8715. HEX6: function(colorO) {
  8716. return ColorUtils.rgb2hex(colorO.r, colorO.g, colorO.b);
  8717. },
  8718. // "FFAA00FF" (last 2 are opacity)
  8719. HEX8: function(colorO) {
  8720. var hex = ColorUtils.rgb2hex(colorO.r, colorO.g, colorO.b),
  8721. opacityHex = Math.round(colorO.a * 255).toString(16);
  8722. if (opacityHex.length < 2) {
  8723. hex += '0';
  8724. }
  8725. hex += opacityHex.toUpperCase();
  8726. return hex;
  8727. }
  8728. },
  8729. /* eslint-disable no-useless-escape */
  8730. hexRe: /^#?(([0-9a-f]{8})|((?:[0-9a-f]{3}){1,2}))$/i,
  8731. rgbaAltRe: /^rgba\(\s*([\w#\d]+)\s*,\s*([\d\.]+)\s*\)$/i,
  8732. rgbaRe: /^rgba\(\s*([\d\.]+)\s*,\s*([\d\.]+)\s*,\s*([\d\.]+)\s*,\s*([\d\.]+)\s*\)$/i,
  8733. rgbRe: /^rgb\(\s*([\d\.]+)\s*,\s*([\d\.]+)\s*,\s*([\d\.]+)\s*\)$/i,
  8734. /* eslint-enable no-useless-escape */
  8735. /**
  8736. * Turn a string to a color object. Supports these formats:
  8737. *
  8738. * - "#ABC" (HEX short)
  8739. * - "#ABCDEF" (HEX)
  8740. * - "#ABCDEFDD" (HEX with opacity)
  8741. * - "red" (named colors - see
  8742. * [Web Colors](http://en.wikipedia.org/wiki/Web_colors) for a full list)
  8743. * - "rgba(r,g,b,a)" i.e "rgba(255,0,0,1)" (a == alpha == 0-1)
  8744. * - "rgba(red, 0.4)"
  8745. * - "rgba(#ABC, 0.9)"
  8746. * - "rgba(#ABCDEF, 0.8)"
  8747. *
  8748. * @param {String} color The color string to parse.
  8749. * @param {String} alphaFormat The format of decimal places for the Alpha channel.
  8750. * @return {Object} Object with various color properties.
  8751. * @return {Number} return.r The red component (0-255).
  8752. * @return {Number} return.g The green component (0-255).
  8753. * @return {Number} return.b The blue component (0-255).
  8754. * @return {Number} return.a The red component (0-1).
  8755. * @return {Number} return.h The hue component (0-1).
  8756. * @return {Number} return.s The saturation component (0-1).
  8757. * @return {Number} return.v The value component (0-1).
  8758. */
  8759. parseColor: function(color, alphaFormat) {
  8760. if (!color) {
  8761. return null;
  8762. }
  8763. // eslint-disable-next-line vars-on-top
  8764. var me = this,
  8765. rgb = me.colorMap[color],
  8766. match, ret, hsv;
  8767. if (rgb) {
  8768. ret = {
  8769. r: rgb[0],
  8770. g: rgb[1],
  8771. b: rgb[2],
  8772. a: 1
  8773. };
  8774. } else if (color === 'transparent') {
  8775. ret = {
  8776. r: 0,
  8777. g: 0,
  8778. b: 0,
  8779. a: 0
  8780. };
  8781. } else {
  8782. match = me.hexRe.exec(color);
  8783. if (match) {
  8784. match = match[1];
  8785. // the captured hex
  8786. switch (match.length) {
  8787. default:
  8788. return null;
  8789. case 3:
  8790. ret = {
  8791. // double the number (e.g. 6 - > 66, a -> aa) and convert to decimal
  8792. r: parseInt(match[0] + match[0], 16),
  8793. g: parseInt(match[1] + match[1], 16),
  8794. b: parseInt(match[2] + match[2], 16),
  8795. a: 1
  8796. };
  8797. break;
  8798. case 6:
  8799. case 8:
  8800. ret = {
  8801. r: parseInt(match.substr(0, 2), 16),
  8802. g: parseInt(match.substr(2, 2), 16),
  8803. b: parseInt(match.substr(4, 2), 16),
  8804. a: parseInt(match.substr(6, 2) || 'ff', 16) / 255
  8805. };
  8806. break;
  8807. }
  8808. } else {
  8809. match = me.rgbaRe.exec(color);
  8810. if (match) {
  8811. // proper css => rgba(r,g,b,a)
  8812. ret = {
  8813. r: parseFloat(match[1]),
  8814. g: parseFloat(match[2]),
  8815. b: parseFloat(match[3]),
  8816. a: parseFloat(match[4])
  8817. };
  8818. } else {
  8819. match = me.rgbaAltRe.exec(color);
  8820. if (match) {
  8821. // scss shorthands = rgba(red, 0.4), rgba(#222, 0.9), rgba(#444433, 0.8)
  8822. ret = me.parseColor(match[1]);
  8823. // we have HSV filled in, so poke on "a" and we're done
  8824. ret.a = parseFloat(match[2]);
  8825. return ret;
  8826. }
  8827. match = me.rgbRe.exec(color);
  8828. if (match) {
  8829. ret = {
  8830. r: parseFloat(match[1]),
  8831. g: parseFloat(match[2]),
  8832. b: parseFloat(match[3]),
  8833. a: 1
  8834. };
  8835. } else {
  8836. return null;
  8837. }
  8838. }
  8839. }
  8840. }
  8841. // format alpha channel
  8842. if (alphaFormat) {
  8843. ret.a = Ext.util.Format.number(ret.a, alphaFormat);
  8844. }
  8845. hsv = this.rgb2hsv(ret.r, ret.g, ret.b);
  8846. return Ext.apply(ret, hsv);
  8847. },
  8848. isValid: function(color) {
  8849. return ColorUtils.parseColor(color) !== null;
  8850. },
  8851. /**
  8852. *
  8853. * @param rgba
  8854. * @return {String}
  8855. */
  8856. getRGBAString: function(rgba) {
  8857. return "rgba(" + rgba.r + "," + rgba.g + "," + rgba.b + "," + rgba.a + ")";
  8858. },
  8859. /**
  8860. * Returns a rgb css string whith this color (without the alpha channel)
  8861. * @param rgb
  8862. * @return {String}
  8863. */
  8864. getRGBString: function(rgb) {
  8865. return "rgb(" + rgb.r + "," + rgb.g + "," + rgb.b + ")";
  8866. },
  8867. /**
  8868. * Following standard math to convert from hsl to rgb
  8869. * Check out wikipedia page for more information on how this works
  8870. * h => [0,1]
  8871. * s,l => [0,1]
  8872. * @param h
  8873. * @param s
  8874. * @param v
  8875. * @return {Object} An object with "r", "g" and "b" color properties.
  8876. */
  8877. hsv2rgb: function(h, s, v) {
  8878. h = h * 360;
  8879. if (h === 360) {
  8880. h = 0;
  8881. }
  8882. // eslint-disable-next-line vars-on-top
  8883. var c = v * s,
  8884. hprime = h / 60,
  8885. x = c * (1 - Math.abs(hprime % 2 - 1)),
  8886. rgb = [
  8887. 0,
  8888. 0,
  8889. 0
  8890. ],
  8891. m;
  8892. switch (Math.floor(hprime)) {
  8893. case 0:
  8894. rgb = [
  8895. c,
  8896. x,
  8897. 0
  8898. ];
  8899. break;
  8900. case 1:
  8901. rgb = [
  8902. x,
  8903. c,
  8904. 0
  8905. ];
  8906. break;
  8907. case 2:
  8908. rgb = [
  8909. 0,
  8910. c,
  8911. x
  8912. ];
  8913. break;
  8914. case 3:
  8915. rgb = [
  8916. 0,
  8917. x,
  8918. c
  8919. ];
  8920. break;
  8921. case 4:
  8922. rgb = [
  8923. x,
  8924. 0,
  8925. c
  8926. ];
  8927. break;
  8928. case 5:
  8929. rgb = [
  8930. c,
  8931. 0,
  8932. x
  8933. ];
  8934. break;
  8935. default:
  8936. //<debug>
  8937. console.error("unknown color " + h + ' ' + s + " " + v);
  8938. //</debug>
  8939. break;
  8940. }
  8941. m = v - c;
  8942. rgb[0] += m;
  8943. rgb[1] += m;
  8944. rgb[2] += m;
  8945. rgb[0] = Math.round(rgb[0] * 255);
  8946. rgb[1] = Math.round(rgb[1] * 255);
  8947. rgb[2] = Math.round(rgb[2] * 255);
  8948. return {
  8949. r: rgb[0],
  8950. g: rgb[1],
  8951. b: rgb[2]
  8952. };
  8953. },
  8954. /**
  8955. * http://en.wikipedia.org/wiki/HSL_and_HSV
  8956. * @param {Number} r The red component (0-255).
  8957. * @param {Number} g The green component (0-255).
  8958. * @param {Number} b The blue component (0-255).
  8959. * @return {Object} An object with "h", "s" and "v" color properties.
  8960. */
  8961. rgb2hsv: function(r, g, b) {
  8962. r = r / 255;
  8963. g = g / 255;
  8964. b = b / 255;
  8965. // eslint-disable-next-line vars-on-top
  8966. var M = Math.max(r, g, b),
  8967. m = Math.min(r, g, b),
  8968. c = M - m,
  8969. hprime = 0,
  8970. s = 0,
  8971. h, v;
  8972. if (c !== 0) {
  8973. if (M === r) {
  8974. hprime = ((g - b) / c) % 6;
  8975. } else if (M === g) {
  8976. hprime = ((b - r) / c) + 2;
  8977. } else if (M === b) {
  8978. hprime = ((r - g) / c) + 4;
  8979. }
  8980. }
  8981. h = hprime * 60;
  8982. if (h === 360) {
  8983. h = 0;
  8984. }
  8985. v = M;
  8986. if (c !== 0) {
  8987. s = c / v;
  8988. }
  8989. h = h / 360;
  8990. if (h < 0) {
  8991. h = h + 1;
  8992. }
  8993. return {
  8994. h: h,
  8995. s: s,
  8996. v: v
  8997. };
  8998. },
  8999. /**
  9000. *
  9001. * @param r
  9002. * @param g
  9003. * @param b
  9004. * @return {String}
  9005. */
  9006. rgb2hex: function(r, g, b) {
  9007. r = r.toString(16);
  9008. g = g.toString(16);
  9009. b = b.toString(16);
  9010. if (r.length < 2) {
  9011. r = '0' + r;
  9012. }
  9013. if (g.length < 2) {
  9014. g = '0' + g;
  9015. }
  9016. if (b.length < 2) {
  9017. b = '0' + b;
  9018. }
  9019. return (r + g + b).toUpperCase();
  9020. },
  9021. colorMap: {
  9022. aliceblue: [
  9023. 240,
  9024. 248,
  9025. 255
  9026. ],
  9027. antiquewhite: [
  9028. 250,
  9029. 235,
  9030. 215
  9031. ],
  9032. aqua: [
  9033. 0,
  9034. 255,
  9035. 255
  9036. ],
  9037. aquamarine: [
  9038. 127,
  9039. 255,
  9040. 212
  9041. ],
  9042. azure: [
  9043. 240,
  9044. 255,
  9045. 255
  9046. ],
  9047. beige: [
  9048. 245,
  9049. 245,
  9050. 220
  9051. ],
  9052. bisque: [
  9053. 255,
  9054. 228,
  9055. 196
  9056. ],
  9057. black: [
  9058. 0,
  9059. 0,
  9060. 0
  9061. ],
  9062. blanchedalmond: [
  9063. 255,
  9064. 235,
  9065. 205
  9066. ],
  9067. blue: [
  9068. 0,
  9069. 0,
  9070. 255
  9071. ],
  9072. blueviolet: [
  9073. 138,
  9074. 43,
  9075. 226
  9076. ],
  9077. brown: [
  9078. 165,
  9079. 42,
  9080. 42
  9081. ],
  9082. burlywood: [
  9083. 222,
  9084. 184,
  9085. 135
  9086. ],
  9087. cadetblue: [
  9088. 95,
  9089. 158,
  9090. 160
  9091. ],
  9092. chartreuse: [
  9093. 127,
  9094. 255,
  9095. 0
  9096. ],
  9097. chocolate: [
  9098. 210,
  9099. 105,
  9100. 30
  9101. ],
  9102. coral: [
  9103. 255,
  9104. 127,
  9105. 80
  9106. ],
  9107. cornflowerblue: [
  9108. 100,
  9109. 149,
  9110. 237
  9111. ],
  9112. cornsilk: [
  9113. 255,
  9114. 248,
  9115. 220
  9116. ],
  9117. crimson: [
  9118. 220,
  9119. 20,
  9120. 60
  9121. ],
  9122. cyan: [
  9123. 0,
  9124. 255,
  9125. 255
  9126. ],
  9127. darkblue: [
  9128. 0,
  9129. 0,
  9130. 139
  9131. ],
  9132. darkcyan: [
  9133. 0,
  9134. 139,
  9135. 139
  9136. ],
  9137. darkgoldenrod: [
  9138. 184,
  9139. 132,
  9140. 11
  9141. ],
  9142. darkgray: [
  9143. 169,
  9144. 169,
  9145. 169
  9146. ],
  9147. darkgreen: [
  9148. 0,
  9149. 100,
  9150. 0
  9151. ],
  9152. darkgrey: [
  9153. 169,
  9154. 169,
  9155. 169
  9156. ],
  9157. darkkhaki: [
  9158. 189,
  9159. 183,
  9160. 107
  9161. ],
  9162. darkmagenta: [
  9163. 139,
  9164. 0,
  9165. 139
  9166. ],
  9167. darkolivegreen: [
  9168. 85,
  9169. 107,
  9170. 47
  9171. ],
  9172. darkorange: [
  9173. 255,
  9174. 140,
  9175. 0
  9176. ],
  9177. darkorchid: [
  9178. 153,
  9179. 50,
  9180. 204
  9181. ],
  9182. darkred: [
  9183. 139,
  9184. 0,
  9185. 0
  9186. ],
  9187. darksalmon: [
  9188. 233,
  9189. 150,
  9190. 122
  9191. ],
  9192. darkseagreen: [
  9193. 143,
  9194. 188,
  9195. 143
  9196. ],
  9197. darkslateblue: [
  9198. 72,
  9199. 61,
  9200. 139
  9201. ],
  9202. darkslategray: [
  9203. 47,
  9204. 79,
  9205. 79
  9206. ],
  9207. darkslategrey: [
  9208. 47,
  9209. 79,
  9210. 79
  9211. ],
  9212. darkturquoise: [
  9213. 0,
  9214. 206,
  9215. 209
  9216. ],
  9217. darkviolet: [
  9218. 148,
  9219. 0,
  9220. 211
  9221. ],
  9222. deeppink: [
  9223. 255,
  9224. 20,
  9225. 147
  9226. ],
  9227. deepskyblue: [
  9228. 0,
  9229. 191,
  9230. 255
  9231. ],
  9232. dimgray: [
  9233. 105,
  9234. 105,
  9235. 105
  9236. ],
  9237. dimgrey: [
  9238. 105,
  9239. 105,
  9240. 105
  9241. ],
  9242. dodgerblue: [
  9243. 30,
  9244. 144,
  9245. 255
  9246. ],
  9247. firebrick: [
  9248. 178,
  9249. 34,
  9250. 34
  9251. ],
  9252. floralwhite: [
  9253. 255,
  9254. 255,
  9255. 240
  9256. ],
  9257. forestgreen: [
  9258. 34,
  9259. 139,
  9260. 34
  9261. ],
  9262. fuchsia: [
  9263. 255,
  9264. 0,
  9265. 255
  9266. ],
  9267. gainsboro: [
  9268. 220,
  9269. 220,
  9270. 220
  9271. ],
  9272. ghostwhite: [
  9273. 248,
  9274. 248,
  9275. 255
  9276. ],
  9277. gold: [
  9278. 255,
  9279. 215,
  9280. 0
  9281. ],
  9282. goldenrod: [
  9283. 218,
  9284. 165,
  9285. 32
  9286. ],
  9287. gray: [
  9288. 128,
  9289. 128,
  9290. 128
  9291. ],
  9292. green: [
  9293. 0,
  9294. 128,
  9295. 0
  9296. ],
  9297. greenyellow: [
  9298. 173,
  9299. 255,
  9300. 47
  9301. ],
  9302. grey: [
  9303. 128,
  9304. 128,
  9305. 128
  9306. ],
  9307. honeydew: [
  9308. 240,
  9309. 255,
  9310. 240
  9311. ],
  9312. hotpink: [
  9313. 255,
  9314. 105,
  9315. 180
  9316. ],
  9317. indianred: [
  9318. 205,
  9319. 92,
  9320. 92
  9321. ],
  9322. indigo: [
  9323. 75,
  9324. 0,
  9325. 130
  9326. ],
  9327. ivory: [
  9328. 255,
  9329. 255,
  9330. 240
  9331. ],
  9332. khaki: [
  9333. 240,
  9334. 230,
  9335. 140
  9336. ],
  9337. lavender: [
  9338. 230,
  9339. 230,
  9340. 250
  9341. ],
  9342. lavenderblush: [
  9343. 255,
  9344. 240,
  9345. 245
  9346. ],
  9347. lawngreen: [
  9348. 124,
  9349. 252,
  9350. 0
  9351. ],
  9352. lemonchiffon: [
  9353. 255,
  9354. 250,
  9355. 205
  9356. ],
  9357. lightblue: [
  9358. 173,
  9359. 216,
  9360. 230
  9361. ],
  9362. lightcoral: [
  9363. 240,
  9364. 128,
  9365. 128
  9366. ],
  9367. lightcyan: [
  9368. 224,
  9369. 255,
  9370. 255
  9371. ],
  9372. lightgoldenrodyellow: [
  9373. 250,
  9374. 250,
  9375. 210
  9376. ],
  9377. lightgray: [
  9378. 211,
  9379. 211,
  9380. 211
  9381. ],
  9382. lightgreen: [
  9383. 144,
  9384. 238,
  9385. 144
  9386. ],
  9387. lightgrey: [
  9388. 211,
  9389. 211,
  9390. 211
  9391. ],
  9392. lightpink: [
  9393. 255,
  9394. 182,
  9395. 193
  9396. ],
  9397. lightsalmon: [
  9398. 255,
  9399. 160,
  9400. 122
  9401. ],
  9402. lightseagreen: [
  9403. 32,
  9404. 178,
  9405. 170
  9406. ],
  9407. lightskyblue: [
  9408. 135,
  9409. 206,
  9410. 250
  9411. ],
  9412. lightslategray: [
  9413. 119,
  9414. 136,
  9415. 153
  9416. ],
  9417. lightslategrey: [
  9418. 119,
  9419. 136,
  9420. 153
  9421. ],
  9422. lightsteelblue: [
  9423. 176,
  9424. 196,
  9425. 222
  9426. ],
  9427. lightyellow: [
  9428. 255,
  9429. 255,
  9430. 224
  9431. ],
  9432. lime: [
  9433. 0,
  9434. 255,
  9435. 0
  9436. ],
  9437. limegreen: [
  9438. 50,
  9439. 205,
  9440. 50
  9441. ],
  9442. linen: [
  9443. 250,
  9444. 240,
  9445. 230
  9446. ],
  9447. magenta: [
  9448. 255,
  9449. 0,
  9450. 255
  9451. ],
  9452. maroon: [
  9453. 128,
  9454. 0,
  9455. 0
  9456. ],
  9457. mediumaquamarine: [
  9458. 102,
  9459. 205,
  9460. 170
  9461. ],
  9462. mediumblue: [
  9463. 0,
  9464. 0,
  9465. 205
  9466. ],
  9467. mediumorchid: [
  9468. 186,
  9469. 85,
  9470. 211
  9471. ],
  9472. mediumpurple: [
  9473. 147,
  9474. 112,
  9475. 219
  9476. ],
  9477. mediumseagreen: [
  9478. 60,
  9479. 179,
  9480. 113
  9481. ],
  9482. mediumslateblue: [
  9483. 123,
  9484. 104,
  9485. 238
  9486. ],
  9487. mediumspringgreen: [
  9488. 0,
  9489. 250,
  9490. 154
  9491. ],
  9492. mediumturquoise: [
  9493. 72,
  9494. 209,
  9495. 204
  9496. ],
  9497. mediumvioletred: [
  9498. 199,
  9499. 21,
  9500. 133
  9501. ],
  9502. midnightblue: [
  9503. 25,
  9504. 25,
  9505. 112
  9506. ],
  9507. mintcream: [
  9508. 245,
  9509. 255,
  9510. 250
  9511. ],
  9512. mistyrose: [
  9513. 255,
  9514. 228,
  9515. 225
  9516. ],
  9517. moccasin: [
  9518. 255,
  9519. 228,
  9520. 181
  9521. ],
  9522. navajowhite: [
  9523. 255,
  9524. 222,
  9525. 173
  9526. ],
  9527. navy: [
  9528. 0,
  9529. 0,
  9530. 128
  9531. ],
  9532. oldlace: [
  9533. 253,
  9534. 245,
  9535. 230
  9536. ],
  9537. olive: [
  9538. 128,
  9539. 128,
  9540. 0
  9541. ],
  9542. olivedrab: [
  9543. 107,
  9544. 142,
  9545. 35
  9546. ],
  9547. orange: [
  9548. 255,
  9549. 165,
  9550. 0
  9551. ],
  9552. orangered: [
  9553. 255,
  9554. 69,
  9555. 0
  9556. ],
  9557. orchid: [
  9558. 218,
  9559. 112,
  9560. 214
  9561. ],
  9562. palegoldenrod: [
  9563. 238,
  9564. 232,
  9565. 170
  9566. ],
  9567. palegreen: [
  9568. 152,
  9569. 251,
  9570. 152
  9571. ],
  9572. paleturquoise: [
  9573. 175,
  9574. 238,
  9575. 238
  9576. ],
  9577. palevioletred: [
  9578. 219,
  9579. 112,
  9580. 147
  9581. ],
  9582. papayawhip: [
  9583. 255,
  9584. 239,
  9585. 213
  9586. ],
  9587. peachpuff: [
  9588. 255,
  9589. 218,
  9590. 185
  9591. ],
  9592. peru: [
  9593. 205,
  9594. 133,
  9595. 63
  9596. ],
  9597. pink: [
  9598. 255,
  9599. 192,
  9600. 203
  9601. ],
  9602. plum: [
  9603. 221,
  9604. 160,
  9605. 203
  9606. ],
  9607. powderblue: [
  9608. 176,
  9609. 224,
  9610. 230
  9611. ],
  9612. purple: [
  9613. 128,
  9614. 0,
  9615. 128
  9616. ],
  9617. red: [
  9618. 255,
  9619. 0,
  9620. 0
  9621. ],
  9622. rosybrown: [
  9623. 188,
  9624. 143,
  9625. 143
  9626. ],
  9627. royalblue: [
  9628. 65,
  9629. 105,
  9630. 225
  9631. ],
  9632. saddlebrown: [
  9633. 139,
  9634. 69,
  9635. 19
  9636. ],
  9637. salmon: [
  9638. 250,
  9639. 128,
  9640. 114
  9641. ],
  9642. sandybrown: [
  9643. 244,
  9644. 164,
  9645. 96
  9646. ],
  9647. seagreen: [
  9648. 46,
  9649. 139,
  9650. 87
  9651. ],
  9652. seashell: [
  9653. 255,
  9654. 245,
  9655. 238
  9656. ],
  9657. sienna: [
  9658. 160,
  9659. 82,
  9660. 45
  9661. ],
  9662. silver: [
  9663. 192,
  9664. 192,
  9665. 192
  9666. ],
  9667. skyblue: [
  9668. 135,
  9669. 206,
  9670. 235
  9671. ],
  9672. slateblue: [
  9673. 106,
  9674. 90,
  9675. 205
  9676. ],
  9677. slategray: [
  9678. 119,
  9679. 128,
  9680. 144
  9681. ],
  9682. slategrey: [
  9683. 119,
  9684. 128,
  9685. 144
  9686. ],
  9687. snow: [
  9688. 255,
  9689. 255,
  9690. 250
  9691. ],
  9692. springgreen: [
  9693. 0,
  9694. 255,
  9695. 127
  9696. ],
  9697. steelblue: [
  9698. 70,
  9699. 130,
  9700. 180
  9701. ],
  9702. tan: [
  9703. 210,
  9704. 180,
  9705. 140
  9706. ],
  9707. teal: [
  9708. 0,
  9709. 128,
  9710. 128
  9711. ],
  9712. thistle: [
  9713. 216,
  9714. 191,
  9715. 216
  9716. ],
  9717. tomato: [
  9718. 255,
  9719. 99,
  9720. 71
  9721. ],
  9722. turquoise: [
  9723. 64,
  9724. 224,
  9725. 208
  9726. ],
  9727. violet: [
  9728. 238,
  9729. 130,
  9730. 238
  9731. ],
  9732. wheat: [
  9733. 245,
  9734. 222,
  9735. 179
  9736. ],
  9737. white: [
  9738. 255,
  9739. 255,
  9740. 255
  9741. ],
  9742. whitesmoke: [
  9743. 245,
  9744. 245,
  9745. 245
  9746. ],
  9747. yellow: [
  9748. 255,
  9749. 255,
  9750. 0
  9751. ],
  9752. yellowgreen: [
  9753. 154,
  9754. 205,
  9755. 5
  9756. ]
  9757. }
  9758. };
  9759. }, function(ColorUtils) {
  9760. var formats = ColorUtils.formats,
  9761. lowerized = {};
  9762. formats['#HEX6'] = function(color) {
  9763. return '#' + formats.HEX6(color);
  9764. };
  9765. formats['#HEX8'] = function(color) {
  9766. return '#' + formats.HEX8(color);
  9767. };
  9768. Ext.Object.each(formats, function(name, fn) {
  9769. lowerized[name.toLowerCase()] = function(color) {
  9770. var ret = fn(color);
  9771. return ret.toLowerCase();
  9772. };
  9773. });
  9774. Ext.apply(formats, lowerized);
  9775. });
  9776. /**
  9777. * @private
  9778. */
  9779. Ext.define('Ext.ux.colorpick.ColorMapController', {
  9780. extend: 'Ext.app.ViewController',
  9781. alias: 'controller.colorpickercolormapcontroller',
  9782. requires: [
  9783. 'Ext.ux.colorpick.ColorUtils'
  9784. ],
  9785. // After the component is rendered
  9786. onFirstBoxReady: function() {
  9787. var me = this,
  9788. colorMap = me.getView(),
  9789. dragHandle = colorMap.down('#dragHandle'),
  9790. dd = dragHandle.dd;
  9791. // configure draggable constraints
  9792. dd.constrain = true;
  9793. dd.constrainTo = colorMap.getEl();
  9794. dd.initialConstrainTo = dd.constrainTo;
  9795. // needed otheriwse error EXTJS-13187
  9796. // event handlers
  9797. dd.on('drag', Ext.bind(me.onHandleDrag, me));
  9798. me.mon(colorMap.getEl(), {
  9799. mousedown: me.onMouseDown,
  9800. dragstart: me.onDragStart,
  9801. scope: me
  9802. });
  9803. },
  9804. // Fires when handle is dragged; propagates "handledrag" event on the ColorMap
  9805. // with parameters "percentX" and "percentY", both 0-1, representing the handle
  9806. // position on the color map, relative to the container
  9807. onHandleDrag: function(componentDragger, e) {
  9808. var me = this,
  9809. container = me.getView(),
  9810. // the Color Map
  9811. dragHandle = container.down('#dragHandle'),
  9812. x = dragHandle.getX() - container.getX(),
  9813. y = dragHandle.getY() - container.getY(),
  9814. containerEl = container.getEl(),
  9815. containerWidth = containerEl.getWidth(),
  9816. containerHeight = containerEl.getHeight(),
  9817. xRatio = x / containerWidth,
  9818. yRatio = y / containerHeight;
  9819. // Adjust x/y ratios for dragger always being 1 pixel from the edge on the right
  9820. if (xRatio > 0.99) {
  9821. xRatio = 1;
  9822. }
  9823. if (yRatio > 0.99) {
  9824. yRatio = 1;
  9825. }
  9826. container.fireEvent('handledrag', xRatio, yRatio);
  9827. },
  9828. // Whenever we mousedown over the colormap area
  9829. onMouseDown: function(e) {
  9830. var me = this,
  9831. container = me.getView(),
  9832. dragHandle = container.down('#dragHandle');
  9833. // position drag handle accordingly
  9834. dragHandle.setY(e.getY());
  9835. dragHandle.setX(e.getX());
  9836. me.onHandleDrag();
  9837. // tie into the default dd mechanism
  9838. dragHandle.dd.onMouseDown(e, dragHandle.dd.el);
  9839. },
  9840. // Whenever we start a drag over the colormap area
  9841. onDragStart: function(e) {
  9842. var me = this,
  9843. container = me.getView(),
  9844. dragHandle = container.down('#dragHandle');
  9845. // tie into the default dd mechanism
  9846. dragHandle.dd.onDragStart(e, dragHandle.dd.el);
  9847. },
  9848. // Whenever the map is clicked (but not the drag handle) we need to position
  9849. // the drag handle to the point of click
  9850. onMapClick: function(e) {
  9851. var me = this,
  9852. container = me.getView(),
  9853. // the Color Map
  9854. dragHandle = container.down('#dragHandle'),
  9855. cXY = container.getXY(),
  9856. eXY = e.getXY(),
  9857. left, top;
  9858. left = eXY[0] - cXY[0];
  9859. top = eXY[1] - cXY[1];
  9860. dragHandle.getEl().setStyle({
  9861. left: left + 'px',
  9862. top: top + 'px'
  9863. });
  9864. me.onHandleDrag();
  9865. },
  9866. // Whenever the underlying binding data is changed we need to
  9867. // update position of the dragger; active drag state has been
  9868. // accounted for earlier
  9869. onColorBindingChanged: function(selectedColor) {
  9870. var me = this,
  9871. vm = me.getViewModel(),
  9872. rgba = vm.get('selectedColor'),
  9873. container = me.getView(),
  9874. // the Color Map
  9875. dragHandle = container.down('#dragHandle'),
  9876. containerEl = container.getEl(),
  9877. containerWidth = containerEl.getWidth(),
  9878. containerHeight = containerEl.getHeight(),
  9879. hsv, xRatio, yRatio, left, top;
  9880. // Color map selection really only depends on saturation and value of the color
  9881. hsv = Ext.ux.colorpick.ColorUtils.rgb2hsv(rgba.r, rgba.g, rgba.b);
  9882. // x-axis of color map with value 0-1 translates to saturation
  9883. xRatio = hsv.s;
  9884. left = containerWidth * xRatio;
  9885. // y-axis of color map with value 0-1 translates to reverse of "value"
  9886. yRatio = 1 - hsv.v;
  9887. top = containerHeight * yRatio;
  9888. // Position dragger
  9889. dragHandle.getEl().setStyle({
  9890. left: left + 'px',
  9891. top: top + 'px'
  9892. });
  9893. },
  9894. // Whenever only Hue changes we can update the
  9895. // background color of the color map
  9896. // Param "hue" has value of 0-1
  9897. onHueBindingChanged: function(hue) {
  9898. var me = this,
  9899. fullColorRGB, hex;
  9900. fullColorRGB = Ext.ux.colorpick.ColorUtils.hsv2rgb(hue, 1, 1);
  9901. hex = Ext.ux.colorpick.ColorUtils.rgb2hex(fullColorRGB.r, fullColorRGB.g, fullColorRGB.b);
  9902. me.getView().getEl().applyStyles({
  9903. 'background-color': '#' + hex
  9904. });
  9905. }
  9906. });
  9907. /**
  9908. * The main colorful square for selecting color shades by dragging around the
  9909. * little circle.
  9910. * @private
  9911. */
  9912. Ext.define('Ext.ux.colorpick.ColorMap', {
  9913. extend: 'Ext.container.Container',
  9914. alias: 'widget.colorpickercolormap',
  9915. controller: 'colorpickercolormapcontroller',
  9916. requires: [
  9917. 'Ext.ux.colorpick.ColorMapController'
  9918. ],
  9919. cls: Ext.baseCSSPrefix + 'colorpicker-colormap',
  9920. // This is the drag "circle"; note it's 1x1 in size to allow full
  9921. // travel around the color map; the inner div has the bigger image
  9922. items: [
  9923. {
  9924. xtype: 'component',
  9925. cls: Ext.baseCSSPrefix + 'colorpicker-colormap-draghandle-container',
  9926. itemId: 'dragHandle',
  9927. width: 1,
  9928. height: 1,
  9929. draggable: true,
  9930. html: '<div class="' + Ext.baseCSSPrefix + 'colorpicker-colormap-draghandle"></div>'
  9931. }
  9932. ],
  9933. listeners: {
  9934. boxready: {
  9935. single: true,
  9936. fn: 'onFirstBoxReady',
  9937. scope: 'controller'
  9938. },
  9939. colorbindingchanged: {
  9940. fn: 'onColorBindingChanged',
  9941. scope: 'controller'
  9942. },
  9943. huebindingchanged: {
  9944. fn: 'onHueBindingChanged',
  9945. scope: 'controller'
  9946. }
  9947. },
  9948. afterRender: function() {
  9949. var me = this,
  9950. src = me.mapGradientUrl,
  9951. el = me.el;
  9952. me.callParent();
  9953. if (!src) {
  9954. // We do this trick to allow the Sass to calculate resource image path for
  9955. // our package and pick up the proper image URL here.
  9956. src = el.getStyle('background-image');
  9957. src = src.substring(4, src.length - 1);
  9958. // strip off outer "url(...)"
  9959. // In IE8 this path will have quotes around it
  9960. if (src.indexOf('"') === 0) {
  9961. src = src.substring(1, src.length - 1);
  9962. }
  9963. // Then remember it on our prototype for any subsequent instances.
  9964. Ext.ux.colorpick.ColorMap.prototype.mapGradientUrl = src;
  9965. }
  9966. // Now clear that style because it will conflict with the background-color
  9967. el.setStyle('background-image', 'none');
  9968. // Create the image with transparent PNG with black and white gradient shades;
  9969. // it blends with the background color (which changes with hue selection). This
  9970. // must be an IMG in order to properly stretch to fit.
  9971. el = me.layout.getElementTarget();
  9972. // the el for items and html
  9973. el.createChild({
  9974. tag: 'img',
  9975. cls: Ext.baseCSSPrefix + 'colorpicker-colormap-blender',
  9976. src: src
  9977. });
  9978. },
  9979. // Called via data binding whenever selectedColor changes; fires "colorbindingchanged"
  9980. setPosition: function(data) {
  9981. var me = this,
  9982. dragHandle = me.down('#dragHandle');
  9983. // Too early in the render cycle? Skip event
  9984. if (!dragHandle.dd || !dragHandle.dd.constrain) {
  9985. return;
  9986. }
  9987. // User actively dragging? Skip event
  9988. if (typeof dragHandle.dd.dragEnded !== 'undefined' && !dragHandle.dd.dragEnded) {
  9989. return;
  9990. }
  9991. me.fireEvent('colorbindingchanged', data);
  9992. },
  9993. // Called via data binding whenever selectedColor.h changes; fires "huebindingchanged" event
  9994. setHue: function(hue) {
  9995. var me = this;
  9996. // Too early in the render cycle? Skip event
  9997. if (!me.getEl()) {
  9998. return;
  9999. }
  10000. me.fireEvent('huebindingchanged', hue);
  10001. }
  10002. });
  10003. /**
  10004. * View Model that holds the "selectedColor" of the color picker container.
  10005. */
  10006. Ext.define('Ext.ux.colorpick.SelectorModel', {
  10007. extend: 'Ext.app.ViewModel',
  10008. alias: 'viewmodel.colorpick-selectormodel',
  10009. requires: [
  10010. 'Ext.ux.colorpick.ColorUtils'
  10011. ],
  10012. data: {
  10013. selectedColor: {
  10014. r: 255,
  10015. // red
  10016. g: 255,
  10017. // green
  10018. b: 255,
  10019. // blue
  10020. h: 0,
  10021. // hue,
  10022. s: 1,
  10023. // saturation
  10024. v: 1,
  10025. // value
  10026. a: 1
  10027. },
  10028. // alpha (opacity)
  10029. previousColor: {
  10030. r: 0,
  10031. // red
  10032. g: 0,
  10033. // green
  10034. b: 0,
  10035. // blue
  10036. h: 0,
  10037. // hue,
  10038. s: 1,
  10039. // saturation
  10040. v: 1,
  10041. // value
  10042. a: 1
  10043. }
  10044. },
  10045. // alpha (opacity)
  10046. formulas: {
  10047. // Hexadecimal representation of the color
  10048. hex: {
  10049. get: function(get) {
  10050. var r = get('selectedColor.r').toString(16),
  10051. g = get('selectedColor.g').toString(16),
  10052. b = get('selectedColor.b').toString(16),
  10053. result;
  10054. result = Ext.ux.colorpick.ColorUtils.rgb2hex(r, g, b);
  10055. return '#' + result;
  10056. },
  10057. set: function(hex) {
  10058. var rgb = Ext.ux.colorpick.ColorUtils.parseColor(hex);
  10059. this.changeRGB(rgb);
  10060. }
  10061. },
  10062. // "R" in "RGB"
  10063. red: {
  10064. get: function(get) {
  10065. return get('selectedColor.r');
  10066. },
  10067. set: function(r) {
  10068. this.changeRGB({
  10069. r: r
  10070. });
  10071. }
  10072. },
  10073. // "G" in "RGB"
  10074. green: {
  10075. get: function(get) {
  10076. return get('selectedColor.g');
  10077. },
  10078. set: function(g) {
  10079. this.changeRGB({
  10080. g: g
  10081. });
  10082. }
  10083. },
  10084. // "B" in "RGB"
  10085. blue: {
  10086. get: function(get) {
  10087. return get('selectedColor.b');
  10088. },
  10089. set: function(b) {
  10090. this.changeRGB({
  10091. b: b
  10092. });
  10093. }
  10094. },
  10095. // "H" in HSV
  10096. hue: {
  10097. get: function(get) {
  10098. return get('selectedColor.h') * 360;
  10099. },
  10100. set: function(hue) {
  10101. this.changeHSV({
  10102. h: hue / 360
  10103. });
  10104. }
  10105. },
  10106. // "S" in HSV
  10107. saturation: {
  10108. get: function(get) {
  10109. return get('selectedColor.s') * 100;
  10110. },
  10111. set: function(saturation) {
  10112. this.changeHSV({
  10113. s: saturation / 100
  10114. });
  10115. }
  10116. },
  10117. // "V" in HSV
  10118. value: {
  10119. get: function(get) {
  10120. var v = get('selectedColor.v');
  10121. return v * 100;
  10122. },
  10123. set: function(value) {
  10124. this.changeHSV({
  10125. v: value / 100
  10126. });
  10127. }
  10128. },
  10129. alpha: {
  10130. get: function(data) {
  10131. var a = data('selectedColor.a');
  10132. return a * 100;
  10133. },
  10134. set: function(alpha) {
  10135. this.set('selectedColor', Ext.applyIf({
  10136. a: alpha / 100
  10137. }, this.data.selectedColor));
  10138. }
  10139. }
  10140. },
  10141. // formulas
  10142. changeHSV: function(hsv) {
  10143. var rgb;
  10144. Ext.applyIf(hsv, this.data.selectedColor);
  10145. rgb = Ext.ux.colorpick.ColorUtils.hsv2rgb(hsv.h, hsv.s, hsv.v);
  10146. hsv.r = rgb.r;
  10147. hsv.g = rgb.g;
  10148. hsv.b = rgb.b;
  10149. this.set('selectedColor', hsv);
  10150. },
  10151. changeRGB: function(rgb) {
  10152. var hsv;
  10153. Ext.applyIf(rgb, this.data.selectedColor);
  10154. hsv = Ext.ux.colorpick.ColorUtils.rgb2hsv(rgb.r, rgb.g, rgb.b);
  10155. rgb.h = hsv.h;
  10156. rgb.s = hsv.s;
  10157. rgb.v = hsv.v;
  10158. this.set('selectedColor', rgb);
  10159. }
  10160. });
  10161. /**
  10162. * @private
  10163. */
  10164. Ext.define('Ext.ux.colorpick.SelectorController', {
  10165. extend: 'Ext.app.ViewController',
  10166. alias: 'controller.colorpick-selectorcontroller',
  10167. requires: [
  10168. 'Ext.ux.colorpick.ColorUtils'
  10169. ],
  10170. destroy: function() {
  10171. var me = this,
  10172. view = me.getView(),
  10173. childViewModel = view.childViewModel;
  10174. if (childViewModel) {
  10175. childViewModel.destroy();
  10176. view.childViewModel = null;
  10177. }
  10178. me.callParent();
  10179. },
  10180. changeHSV: function(hsv) {
  10181. var view = this.getView(),
  10182. color = view.getColor(),
  10183. rgb;
  10184. // Put in values we are not changing (like A, but also missing HSV values)
  10185. Ext.applyIf(hsv, color);
  10186. // Now that HSV is complete, recalculate RGB and combine them
  10187. rgb = Ext.ux.colorpick.ColorUtils.hsv2rgb(hsv.h, hsv.s, hsv.v);
  10188. Ext.apply(hsv, rgb);
  10189. view.setColor(hsv);
  10190. },
  10191. // Updates Saturation/Value in the model based on ColorMap; params:
  10192. // xPercent - where is the handle relative to the color map width
  10193. // yPercent - where is the handle relative to the color map height
  10194. onColorMapHandleDrag: function(xPercent, yPercent) {
  10195. this.changeHSV({
  10196. s: xPercent,
  10197. v: 1 - yPercent
  10198. });
  10199. },
  10200. // Updates HSV Value in the model and downstream RGB settings
  10201. onValueSliderHandleDrag: function(yPercent) {
  10202. this.changeHSV({
  10203. v: 1 - yPercent
  10204. });
  10205. },
  10206. // Updates HSV Saturation in the model and downstream RGB settings
  10207. onSaturationSliderHandleDrag: function(yPercent) {
  10208. this.changeHSV({
  10209. s: 1 - yPercent
  10210. });
  10211. },
  10212. // Updates Hue in the model and downstream RGB settings
  10213. onHueSliderHandleDrag: function(yPercent) {
  10214. this.changeHSV({
  10215. h: 1 - yPercent
  10216. });
  10217. },
  10218. onAlphaSliderHandleDrag: function(yPercent) {
  10219. var view = this.getView(),
  10220. color = view.getColor(),
  10221. newColor = Ext.applyIf({
  10222. a: 1 - yPercent
  10223. }, color);
  10224. view.setColor(newColor);
  10225. view.el.repaint();
  10226. },
  10227. onPreviousColorSelected: function(comp, color) {
  10228. var view = this.getView();
  10229. view.setColor(color);
  10230. },
  10231. onOK: function() {
  10232. var me = this,
  10233. view = me.getView();
  10234. view.fireEvent('ok', view, view.getValue());
  10235. },
  10236. onCancel: function() {
  10237. this.fireViewEvent('cancel', this.getView());
  10238. },
  10239. onResize: function() {
  10240. var me = this,
  10241. view = me.getView(),
  10242. vm = view.childViewModel,
  10243. refs = me.getReferences(),
  10244. h, s, v, a;
  10245. // Skip initial rendering resize
  10246. if (!me.hasResizedOnce) {
  10247. me.hasResizedOnce = true;
  10248. return;
  10249. }
  10250. h = vm.get('hue');
  10251. s = vm.get('saturation');
  10252. v = vm.get('value');
  10253. a = vm.get('alpha');
  10254. // Reposition the colormap's & sliders' drag handles
  10255. refs.colorMap.setPosition(vm.getData());
  10256. refs.hueSlider.setHue(h);
  10257. refs.satSlider.setSaturation(s);
  10258. refs.valueSlider.setValue(v);
  10259. refs.alphaSlider.setAlpha(a);
  10260. }
  10261. });
  10262. /**
  10263. * A basic component that changes background color, with considerations for opacity
  10264. * support (checkered background image and IE8 support).
  10265. */
  10266. Ext.define('Ext.ux.colorpick.ColorPreview', {
  10267. extend: 'Ext.Component',
  10268. alias: 'widget.colorpickercolorpreview',
  10269. requires: [
  10270. 'Ext.util.Format',
  10271. 'Ext.XTemplate'
  10272. ],
  10273. // hack to solve issue with IE, when applying a filter the click listener is not being fired.
  10274. style: 'position: relative',
  10275. /* eslint-disable max-len */
  10276. html: '<div class="' + Ext.baseCSSPrefix + 'colorpreview-filter" style="height:100%; width:100%; position: absolute;"></div>' + '<a class="btn" style="height:100%; width:100%; position: absolute;"></a>',
  10277. /* eslint-enable max-len */
  10278. // eo hack
  10279. cls: Ext.baseCSSPrefix + 'colorpreview',
  10280. height: 256,
  10281. onRender: function() {
  10282. var me = this;
  10283. me.callParent(arguments);
  10284. me.mon(me.el.down('.btn'), 'click', me.onClick, me);
  10285. },
  10286. onClick: function() {
  10287. this.fireEvent('click', this, this.color);
  10288. },
  10289. // Called via databinding - update background color whenever ViewModel changes
  10290. setColor: function(color) {
  10291. var me = this,
  10292. el = me.getEl();
  10293. // Too early in rendering cycle; skip
  10294. if (!el) {
  10295. return;
  10296. }
  10297. me.color = color;
  10298. me.applyBgStyle(color);
  10299. },
  10300. bgStyleTpl: Ext.create('Ext.XTemplate', Ext.isIE && Ext.ieVersion < 10 ? // eslint-disable-next-line max-len
  10301. 'filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr=\'#{hexAlpha}{hex}\', endColorstr=\'#{hexAlpha}{hex}\');' : /* IE6-9 */
  10302. 'background: {rgba};'),
  10303. applyBgStyle: function(color) {
  10304. var me = this,
  10305. colorUtils = Ext.ux.colorpick.ColorUtils,
  10306. filterSelector = '.' + Ext.baseCSSPrefix + 'colorpreview-filter',
  10307. el = me.getEl().down(filterSelector),
  10308. hex, alpha, rgba, bgStyle;
  10309. hex = colorUtils.rgb2hex(color.r, color.g, color.b);
  10310. alpha = Ext.util.Format.hex(Math.floor(color.a * 255), 2);
  10311. rgba = colorUtils.getRGBAString(color);
  10312. bgStyle = this.bgStyleTpl.apply({
  10313. hex: hex,
  10314. hexAlpha: alpha,
  10315. rgba: rgba
  10316. });
  10317. el.applyStyles(bgStyle);
  10318. }
  10319. });
  10320. /**
  10321. * @private
  10322. */
  10323. Ext.define('Ext.ux.colorpick.SliderController', {
  10324. extend: 'Ext.app.ViewController',
  10325. alias: 'controller.colorpick-slidercontroller',
  10326. // After the component is rendered
  10327. boxReady: function(view) {
  10328. var me = this,
  10329. container = me.getDragContainer(),
  10330. dragHandle = me.getDragHandle(),
  10331. dd = dragHandle.dd;
  10332. // configure draggable constraints
  10333. dd.constrain = true;
  10334. dd.constrainTo = container.getEl();
  10335. dd.initialConstrainTo = dd.constrainTo;
  10336. // needed otherwise error EXTJS-13187
  10337. // event handlers
  10338. dd.on('drag', me.onHandleDrag, me);
  10339. },
  10340. getDragHandle: function() {
  10341. return this.view.lookupReference('dragHandle');
  10342. },
  10343. getDragContainer: function() {
  10344. return this.view.lookupReference('dragHandleContainer');
  10345. },
  10346. // Fires when handle is dragged; fires "handledrag" event on the slider
  10347. // with parameter "percentY" 0-1, representing the handle position on the slider
  10348. // relative to the height
  10349. onHandleDrag: function(e) {
  10350. var me = this,
  10351. view = me.getView(),
  10352. container = me.getDragContainer(),
  10353. dragHandle = me.getDragHandle(),
  10354. y = dragHandle.getY() - container.getY(),
  10355. containerEl = container.getEl(),
  10356. containerHeight = containerEl.getHeight(),
  10357. yRatio = y / containerHeight;
  10358. // Adjust y ratio for dragger always being 1 pixel from the edge on the bottom
  10359. if (yRatio > 0.99) {
  10360. yRatio = 1;
  10361. }
  10362. view.fireEvent('handledrag', yRatio);
  10363. },
  10364. // Whenever we mousedown over the slider area
  10365. onMouseDown: function(e) {
  10366. var me = this,
  10367. dragHandle = me.getDragHandle(),
  10368. y = e.getY();
  10369. // position drag handle accordingly
  10370. dragHandle.setY(y);
  10371. me.onHandleDrag();
  10372. dragHandle.el.repaint();
  10373. // tie into the default dd mechanism
  10374. dragHandle.dd.onMouseDown(e, dragHandle.dd.el);
  10375. },
  10376. // Whenever we start a drag over the colormap area
  10377. onDragStart: function(e) {
  10378. var me = this,
  10379. dragHandle = me.getDragHandle();
  10380. // tie into the default dd mechanism
  10381. dragHandle.dd.onDragStart(e, dragHandle.dd.el);
  10382. },
  10383. onMouseUp: function() {
  10384. var dragHandle = this.getDragHandle();
  10385. dragHandle.dd.dragEnded = true;
  10386. }
  10387. });
  10388. // work around DragTracker bug
  10389. /**
  10390. * Parent view for the 4 sliders seen on the color picker window.
  10391. * @private
  10392. */
  10393. Ext.define('Ext.ux.colorpick.Slider', {
  10394. extend: 'Ext.container.Container',
  10395. xtype: 'colorpickerslider',
  10396. controller: 'colorpick-slidercontroller',
  10397. afterRender: function() {
  10398. var width, dragCt, dragWidth;
  10399. this.callParent(arguments);
  10400. width = this.width;
  10401. dragCt = this.lookupReference('dragHandleContainer');
  10402. dragWidth = dragCt.getWidth();
  10403. dragCt.el.setStyle('left', ((width - dragWidth) / 2) + 'px');
  10404. },
  10405. baseCls: Ext.baseCSSPrefix + 'colorpicker-slider',
  10406. requires: [
  10407. 'Ext.ux.colorpick.SliderController'
  10408. ],
  10409. referenceHolder: true,
  10410. listeners: {
  10411. element: 'el',
  10412. mousedown: 'onMouseDown',
  10413. mouseup: 'onMouseUp',
  10414. dragstart: 'onDragStart'
  10415. },
  10416. // Container for the drag handle; needed since the slider
  10417. // is of static size, while the parent container positions
  10418. // it in the center; this is what receives the beautiful
  10419. // color gradients for the visual
  10420. items: {
  10421. xtype: 'container',
  10422. cls: Ext.baseCSSPrefix + 'colorpicker-draghandle-container',
  10423. reference: 'dragHandleContainer',
  10424. height: '100%',
  10425. // This is the drag handle; note it's 100%x1 in size to allow full
  10426. // vertical drag travel; the inner div has the bigger image
  10427. items: {
  10428. xtype: 'component',
  10429. cls: Ext.baseCSSPrefix + 'colorpicker-draghandle-outer',
  10430. reference: 'dragHandle',
  10431. width: '100%',
  10432. height: 1,
  10433. draggable: true,
  10434. html: '<div class="' + Ext.baseCSSPrefix + 'colorpicker-draghandle"></div>'
  10435. }
  10436. },
  10437. //<debug>
  10438. // Called via data binding whenever selectedColor.h changes;
  10439. setHue: function() {
  10440. Ext.raise('Must implement setHue() in a child class!');
  10441. },
  10442. //</debug>
  10443. getDragHandle: function() {
  10444. return this.lookupReference('dragHandle');
  10445. },
  10446. getDragContainer: function() {
  10447. return this.lookupReference('dragHandleContainer');
  10448. }
  10449. });
  10450. /**
  10451. * Used for "Alpha" slider.
  10452. * @private
  10453. */
  10454. Ext.define('Ext.ux.colorpick.SliderAlpha', {
  10455. extend: 'Ext.ux.colorpick.Slider',
  10456. alias: 'widget.colorpickerslideralpha',
  10457. cls: Ext.baseCSSPrefix + 'colorpicker-alpha',
  10458. requires: [
  10459. 'Ext.XTemplate'
  10460. ],
  10461. /* eslint-disable max-len */
  10462. gradientStyleTpl: Ext.create('Ext.XTemplate', Ext.isIE && Ext.ieVersion < 10 ? 'filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr=\'#FF{hex}\', endColorstr=\'#00{hex}\');' : /* IE6-9 */
  10463. 'background: -moz-linear-gradient(top, rgba({r}, {g}, {b}, 1) 0%, rgba({r}, {g}, {b}, 0) 100%);' + /* FF3.6+ */
  10464. 'background: -webkit-linear-gradient(top,rgba({r}, {g}, {b}, 1) 0%, rgba({r}, {g}, {b}, 0) 100%);' + /* Chrome10+,Safari5.1+ */
  10465. 'background: -o-linear-gradient(top, rgba({r}, {g}, {b}, 1) 0%, rgba({r}, {g}, {b}, 0) 100%);' + /* Opera 11.10+ */
  10466. 'background: -ms-linear-gradient(top, rgba({r}, {g}, {b}, 1) 0%, rgba({r}, {g}, {b}, 0) 100%);' + /* IE10+ */
  10467. 'background: linear-gradient(to bottom, rgba({r}, {g}, {b}, 1) 0%, rgba({r}, {g}, {b}, 0) 100%);'),
  10468. /* W3C */
  10469. /* eslint-enable max-len */
  10470. // Called via data binding whenever selectedColor.a changes; param is 0-100
  10471. setAlpha: function(value) {
  10472. var me = this,
  10473. container = me.getDragContainer(),
  10474. dragHandle = me.getDragHandle(),
  10475. containerEl = container.getEl(),
  10476. containerHeight = containerEl.getHeight(),
  10477. el, top;
  10478. // Too early in the render cycle? Skip event
  10479. if (!dragHandle.dd || !dragHandle.dd.constrain) {
  10480. return;
  10481. }
  10482. // User actively dragging? Skip event
  10483. if (typeof dragHandle.dd.dragEnded !== 'undefined' && !dragHandle.dd.dragEnded) {
  10484. return;
  10485. }
  10486. // y-axis of slider with value 0-1 translates to reverse of "value"
  10487. top = containerHeight * (1 - (value / 100));
  10488. // Position dragger
  10489. el = dragHandle.getEl();
  10490. el.setStyle({
  10491. top: top + 'px'
  10492. });
  10493. },
  10494. // Called via data binding whenever selectedColor.h changes; hue param is 0-1
  10495. setColor: function(color) {
  10496. var me = this,
  10497. container = me.getDragContainer(),
  10498. hex, el;
  10499. // Too early in the render cycle? Skip event
  10500. if (!me.getEl()) {
  10501. return;
  10502. }
  10503. // Determine HEX for new hue and set as background based on template
  10504. hex = Ext.ux.colorpick.ColorUtils.rgb2hex(color.r, color.g, color.b);
  10505. el = container.getEl().first();
  10506. el.applyStyles(me.gradientStyleTpl.apply({
  10507. hex: hex,
  10508. r: color.r,
  10509. g: color.g,
  10510. b: color.b
  10511. }));
  10512. }
  10513. });
  10514. /**
  10515. * Used for "Saturation" slider
  10516. * @private
  10517. */
  10518. Ext.define('Ext.ux.colorpick.SliderSaturation', {
  10519. extend: 'Ext.ux.colorpick.Slider',
  10520. alias: 'widget.colorpickerslidersaturation',
  10521. cls: Ext.baseCSSPrefix + 'colorpicker-saturation',
  10522. /* eslint-disable max-len */
  10523. gradientStyleTpl: Ext.create('Ext.XTemplate', Ext.isIE && Ext.ieVersion < 10 ? 'filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr=\'#{hex}\', endColorstr=\'#ffffff\');' : /* IE6-9 */
  10524. 'background: -mox-linear-gradient(top, #{hex} 0%, #ffffff 100%);' + /* FF3.6+ */
  10525. 'background: -webkit-linear-gradient(top, #{hex} 0%,#ffffff 100%);' + /* Chrome10+,Safari5.1+ */
  10526. 'background: -o-linear-gradient(top, #{hex} 0%,#ffffff 100%);' + /* Opera 11.10+ */
  10527. 'background: -ms-linear-gradient(top, #{hex} 0%,#ffffff 100%);' + /* IE10+ */
  10528. 'background: linear-gradient(to bottom, #{hex} 0%,#ffffff 100%);'),
  10529. /* W3C */
  10530. /* eslint-enable max-len */
  10531. // Called via data binding whenever selectedColor.s changes; saturation param is 0-100
  10532. setSaturation: function(saturation) {
  10533. var me = this,
  10534. container = me.getDragContainer(),
  10535. dragHandle = me.getDragHandle(),
  10536. containerEl = container.getEl(),
  10537. containerHeight = containerEl.getHeight(),
  10538. yRatio, top;
  10539. // Too early in the render cycle? Skip event
  10540. if (!dragHandle.dd || !dragHandle.dd.constrain) {
  10541. return;
  10542. }
  10543. // User actively dragging? Skip event
  10544. if (typeof dragHandle.dd.dragEnded !== 'undefined' && !dragHandle.dd.dragEnded) {
  10545. return;
  10546. }
  10547. // y-axis of slider with value 0-1 translates to reverse of "saturation"
  10548. yRatio = 1 - (saturation / 100);
  10549. top = containerHeight * yRatio;
  10550. // Position dragger
  10551. dragHandle.getEl().setStyle({
  10552. top: top + 'px'
  10553. });
  10554. },
  10555. // Called via data binding whenever selectedColor.h changes; hue param is 0-1
  10556. setHue: function(hue) {
  10557. var me = this,
  10558. container = me.getDragContainer(),
  10559. rgb, hex;
  10560. // Too early in the render cycle? Skip event
  10561. if (!me.getEl()) {
  10562. return;
  10563. }
  10564. // Determine HEX for new hue and set as background based on template
  10565. rgb = Ext.ux.colorpick.ColorUtils.hsv2rgb(hue, 1, 1);
  10566. hex = Ext.ux.colorpick.ColorUtils.rgb2hex(rgb.r, rgb.g, rgb.b);
  10567. container.getEl().applyStyles(me.gradientStyleTpl.apply({
  10568. hex: hex
  10569. }));
  10570. }
  10571. });
  10572. /**
  10573. * Used for "Value" slider.
  10574. * @private
  10575. */
  10576. Ext.define('Ext.ux.colorpick.SliderValue', {
  10577. extend: 'Ext.ux.colorpick.Slider',
  10578. alias: 'widget.colorpickerslidervalue',
  10579. cls: Ext.baseCSSPrefix + 'colorpicker-value',
  10580. requires: [
  10581. 'Ext.XTemplate'
  10582. ],
  10583. /* eslint-disable max-len */
  10584. gradientStyleTpl: Ext.create('Ext.XTemplate', Ext.isIE && Ext.ieVersion < 10 ? 'filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr=\'#{hex}\', endColorstr=\'#000000\');' : /* IE6-9 */
  10585. 'background: -mox-linear-gradient(top, #{hex} 0%, #000000 100%);' + /* FF3.6+ */
  10586. 'background: -webkit-linear-gradient(top, #{hex} 0%,#000000 100%);' + /* Chrome10+,Safari5.1+ */
  10587. 'background: -o-linear-gradient(top, #{hex} 0%,#000000 100%);' + /* Opera 11.10+ */
  10588. 'background: -ms-linear-gradient(top, #{hex} 0%,#000000 100%);' + /* IE10+ */
  10589. 'background: linear-gradient(to bottom, #{hex} 0%,#000000 100%);'),
  10590. /* W3C */
  10591. /* eslint-enable max-len */
  10592. // Called via data binding whenever selectedColor.v changes; value param is 0-100
  10593. setValue: function(value) {
  10594. var me = this,
  10595. container = me.getDragContainer(),
  10596. dragHandle = me.getDragHandle(),
  10597. containerEl = container.getEl(),
  10598. containerHeight = containerEl.getHeight(),
  10599. yRatio, top;
  10600. // Too early in the render cycle? Skip event
  10601. if (!dragHandle.dd || !dragHandle.dd.constrain) {
  10602. return;
  10603. }
  10604. // User actively dragging? Skip event
  10605. if (typeof dragHandle.dd.dragEnded !== 'undefined' && !dragHandle.dd.dragEnded) {
  10606. return;
  10607. }
  10608. // y-axis of slider with value 0-1 translates to reverse of "value"
  10609. yRatio = 1 - (value / 100);
  10610. top = containerHeight * yRatio;
  10611. // Position dragger
  10612. dragHandle.getEl().setStyle({
  10613. top: top + 'px'
  10614. });
  10615. },
  10616. // Called via data binding whenever selectedColor.h changes; hue param is 0-1
  10617. setHue: function(hue) {
  10618. var me = this,
  10619. container = me.getDragContainer(),
  10620. rgb, hex;
  10621. // Too early in the render cycle? Skip event
  10622. if (!me.getEl()) {
  10623. return;
  10624. }
  10625. // Determine HEX for new hue and set as background based on template
  10626. rgb = Ext.ux.colorpick.ColorUtils.hsv2rgb(hue, 1, 1);
  10627. hex = Ext.ux.colorpick.ColorUtils.rgb2hex(rgb.r, rgb.g, rgb.b);
  10628. container.getEl().applyStyles(me.gradientStyleTpl.apply({
  10629. hex: hex
  10630. }));
  10631. }
  10632. });
  10633. /**
  10634. * Used for "Hue" slider.
  10635. * @private
  10636. */
  10637. Ext.define('Ext.ux.colorpick.SliderHue', {
  10638. extend: 'Ext.ux.colorpick.Slider',
  10639. alias: 'widget.colorpickersliderhue',
  10640. cls: Ext.baseCSSPrefix + 'colorpicker-hue',
  10641. afterRender: function() {
  10642. var me = this,
  10643. src = me.gradientUrl,
  10644. el = me.el;
  10645. me.callParent();
  10646. if (!src) {
  10647. // We do this trick to allow the Sass to calculate resource image path for
  10648. // our package and pick up the proper image URL here.
  10649. src = el.getStyle('background-image');
  10650. src = src.substring(4, src.length - 1);
  10651. // strip off outer "url(...)"
  10652. // In IE8 this path will have quotes around it
  10653. if (src.indexOf('"') === 0) {
  10654. src = src.substring(1, src.length - 1);
  10655. }
  10656. // Then remember it on our prototype for any subsequent instances.
  10657. Ext.ux.colorpick.SliderHue.prototype.gradientUrl = src;
  10658. }
  10659. // Now clear that style because it will conflict with the background-color
  10660. el.setStyle('background-image', 'none');
  10661. // Create the image with the background PNG
  10662. el = me.getDragContainer().layout.getElementTarget();
  10663. // the el for items and html
  10664. el.createChild({
  10665. tag: 'img',
  10666. cls: Ext.baseCSSPrefix + 'colorpicker-hue-gradient',
  10667. src: src
  10668. });
  10669. },
  10670. // Called via data binding whenever selectedColor.h changes; hue param is 0-1
  10671. setHue: function(hue) {
  10672. var me = this,
  10673. container = me.getDragContainer(),
  10674. dragHandle = me.getDragHandle(),
  10675. containerEl = container.getEl(),
  10676. containerHeight = containerEl.getHeight(),
  10677. el, top;
  10678. // Too early in the render cycle? Skip event
  10679. if (!dragHandle.dd || !dragHandle.dd.constrain) {
  10680. return;
  10681. }
  10682. // User actively dragging? Skip event
  10683. if (typeof dragHandle.dd.dragEnded !== 'undefined' && !dragHandle.dd.dragEnded) {
  10684. return;
  10685. }
  10686. // y-axis of slider with value 0-1 translates to reverse of "hue"
  10687. top = containerHeight * (1 - hue);
  10688. // Position dragger
  10689. el = dragHandle.getEl();
  10690. el.setStyle({
  10691. top: top + 'px'
  10692. });
  10693. }
  10694. });
  10695. /**
  10696. * Sencha Pro Services presents xtype "colorselector".
  10697. * API has been kept as close to the regular colorpicker as possible. The Selector can be
  10698. * rendered to any container.
  10699. *
  10700. * The defaul selected color is configurable via {@link #value} config. Usually used in
  10701. * forms via {@link Ext.ux.colorpick.Button} or {@link Ext.ux.colorpick.Field}.
  10702. *
  10703. * Typically you will need to listen for the change event to be notified when the user
  10704. * chooses a color. Alternatively, you can bind to the "value" config
  10705. *
  10706. * @example
  10707. * Ext.create('Ext.ux.colorpick.Selector', {
  10708. * value: '993300', // initial selected color
  10709. * renderTo: Ext.getBody(),
  10710. * listeners: {
  10711. * change: function (colorselector, color) {
  10712. * console.log('New color: ' + color);
  10713. * }
  10714. * }
  10715. * });
  10716. */
  10717. Ext.define('Ext.ux.colorpick.Selector', {
  10718. extend: 'Ext.container.Container',
  10719. xtype: 'colorselector',
  10720. mixins: [
  10721. 'Ext.ux.colorpick.Selection'
  10722. ],
  10723. controller: 'colorpick-selectorcontroller',
  10724. requires: [
  10725. 'Ext.layout.container.HBox',
  10726. 'Ext.form.field.Text',
  10727. 'Ext.form.field.Number',
  10728. 'Ext.ux.colorpick.ColorMap',
  10729. 'Ext.ux.colorpick.SelectorModel',
  10730. 'Ext.ux.colorpick.SelectorController',
  10731. 'Ext.ux.colorpick.ColorPreview',
  10732. 'Ext.ux.colorpick.Slider',
  10733. 'Ext.ux.colorpick.SliderAlpha',
  10734. 'Ext.ux.colorpick.SliderSaturation',
  10735. 'Ext.ux.colorpick.SliderValue',
  10736. 'Ext.ux.colorpick.SliderHue'
  10737. ],
  10738. config: {
  10739. hexReadOnly: true
  10740. },
  10741. width: 580,
  10742. // default width and height gives 255x255 color map in Crisp
  10743. height: 337,
  10744. cls: Ext.baseCSSPrefix + 'colorpicker',
  10745. padding: 10,
  10746. layout: {
  10747. type: 'hbox',
  10748. align: 'stretch'
  10749. },
  10750. defaultBindProperty: 'value',
  10751. twoWayBindable: [
  10752. 'value'
  10753. ],
  10754. /**
  10755. * @cfg fieldWidth {Number} Width of the text fields on the container (excluding HEX);
  10756. * since the width of the slider containers is the same as the text field under it
  10757. * (it's the same vbox column), changing this value will also affect the spacing between
  10758. * the sliders.
  10759. */
  10760. fieldWidth: 50,
  10761. /**
  10762. * @cfg fieldPad {Number} padding between the sliders and HEX/R/G/B fields.
  10763. */
  10764. fieldPad: 5,
  10765. /**
  10766. * @cfg {Boolean} [showPreviousColor]
  10767. * Whether "previous color" region (in upper right, below the selected color preview)
  10768. * should be shown; these are relied upon by the {@link Ext.ux.colorpick.Button} and
  10769. * the {@link Ext.ux.colorpick.Field}.
  10770. */
  10771. showPreviousColor: false,
  10772. /**
  10773. * @cfg {Boolean} [showOkCancelButtons]
  10774. * Whether Ok and Cancel buttons (in upper right, below the selected color preview)
  10775. * should be shown; these are relied upon by the {@link Ext.ux.colorpick.Button} and
  10776. * the {@link Ext.ux.colorpick.Field}.
  10777. */
  10778. showOkCancelButtons: false,
  10779. /**
  10780. * @event change
  10781. * Fires when a color is selected. Simply dragging sliders around will trigger this.
  10782. * @param {Ext.ux.colorpick.Selector} this
  10783. * @param {String} color The value of the selected color as per specified {@link #format}.
  10784. * @param {String} previousColor The previous color value.
  10785. */
  10786. /**
  10787. * @event ok
  10788. * Fires when OK button is clicked (see {@link #showOkCancelButtons}).
  10789. * @param {Ext.ux.colorpick.Selector} this
  10790. * @param {String} color The value of the selected color as per specified {@link #format}.
  10791. */
  10792. /**
  10793. * @event cancel
  10794. * Fires when Cancel button is clicked (see {@link #showOkCancelButtons}).
  10795. * @param {Ext.ux.colorpick.Selector} this
  10796. */
  10797. listeners: {
  10798. resize: 'onResize'
  10799. },
  10800. constructor: function(config) {
  10801. var me = this,
  10802. childViewModel = Ext.Factory.viewModel('colorpick-selectormodel');
  10803. // Since this component needs to present its value as a thing to which users can
  10804. // bind, we create an internal VM for our purposes.
  10805. me.childViewModel = childViewModel;
  10806. me.items = [
  10807. me.getMapAndHexRGBFields(childViewModel),
  10808. me.getSliderAndHField(childViewModel),
  10809. me.getSliderAndSField(childViewModel),
  10810. me.getSliderAndVField(childViewModel),
  10811. me.getSliderAndAField(childViewModel),
  10812. me.getPreviewAndButtons(childViewModel, config)
  10813. ];
  10814. me.childViewModel.bind('{selectedColor}', function(color) {
  10815. me.setColor(color);
  10816. });
  10817. me.callParent([
  10818. config
  10819. ]);
  10820. },
  10821. updateColor: function(color) {
  10822. var me = this;
  10823. me.mixins.colorselection.updateColor.call(me, color);
  10824. me.childViewModel.set('selectedColor', color);
  10825. },
  10826. updatePreviousColor: function(color) {
  10827. this.childViewModel.set('previousColor', color);
  10828. },
  10829. // Splits up view declaration for readability
  10830. // "Map" and HEX/R/G/B fields
  10831. getMapAndHexRGBFields: function(childViewModel) {
  10832. var me = this,
  10833. fieldMargin = {
  10834. top: 0,
  10835. right: me.fieldPad,
  10836. bottom: 0,
  10837. left: 0
  10838. },
  10839. fieldWidth = me.fieldWidth;
  10840. return {
  10841. xtype: 'container',
  10842. viewModel: childViewModel,
  10843. cls: Ext.baseCSSPrefix + 'colorpicker-escape-overflow',
  10844. flex: 1,
  10845. layout: {
  10846. type: 'vbox',
  10847. align: 'stretch'
  10848. },
  10849. margin: '0 10 0 0',
  10850. items: [
  10851. // "MAP"
  10852. {
  10853. xtype: 'colorpickercolormap',
  10854. reference: 'colorMap',
  10855. flex: 1,
  10856. bind: {
  10857. position: {
  10858. bindTo: '{selectedColor}',
  10859. deep: true
  10860. },
  10861. hue: '{selectedColor.h}'
  10862. },
  10863. listeners: {
  10864. handledrag: 'onColorMapHandleDrag'
  10865. }
  10866. },
  10867. // HEX/R/G/B FIELDS
  10868. {
  10869. xtype: 'container',
  10870. layout: 'hbox',
  10871. defaults: {
  10872. labelAlign: 'top',
  10873. labelSeparator: '',
  10874. allowBlank: false,
  10875. onChange: function() {
  10876. // prevent data binding propagation if bad value
  10877. if (this.isValid()) {
  10878. // this is kind of dirty and ideally we would extend these fields
  10879. // and override the method, but works for now
  10880. Ext.form.field.Base.prototype.onChange.apply(this, arguments);
  10881. }
  10882. }
  10883. },
  10884. items: [
  10885. {
  10886. xtype: 'textfield',
  10887. fieldLabel: 'HEX',
  10888. flex: 1,
  10889. bind: '{hex}',
  10890. margin: fieldMargin,
  10891. regex: /^#[0-9a-f]{6}$/i,
  10892. readonly: me.getHexReadOnly()
  10893. },
  10894. {
  10895. xtype: 'numberfield',
  10896. fieldLabel: 'R',
  10897. bind: '{red}',
  10898. width: fieldWidth,
  10899. hideTrigger: true,
  10900. maxValue: 255,
  10901. minValue: 0,
  10902. margin: fieldMargin
  10903. },
  10904. {
  10905. xtype: 'numberfield',
  10906. fieldLabel: 'G',
  10907. bind: '{green}',
  10908. width: fieldWidth,
  10909. hideTrigger: true,
  10910. maxValue: 255,
  10911. minValue: 0,
  10912. margin: fieldMargin
  10913. },
  10914. {
  10915. xtype: 'numberfield',
  10916. fieldLabel: 'B',
  10917. bind: '{blue}',
  10918. width: fieldWidth,
  10919. hideTrigger: true,
  10920. maxValue: 255,
  10921. minValue: 0,
  10922. margin: 0
  10923. }
  10924. ]
  10925. }
  10926. ]
  10927. };
  10928. },
  10929. // Splits up view declaration for readability
  10930. // Slider and H field
  10931. getSliderAndHField: function(childViewModel) {
  10932. var me = this,
  10933. fieldWidth = me.fieldWidth;
  10934. return {
  10935. xtype: 'container',
  10936. viewModel: childViewModel,
  10937. cls: Ext.baseCSSPrefix + 'colorpicker-escape-overflow',
  10938. width: fieldWidth,
  10939. layout: {
  10940. type: 'vbox',
  10941. align: 'stretch'
  10942. },
  10943. items: [
  10944. {
  10945. xtype: 'colorpickersliderhue',
  10946. reference: 'hueSlider',
  10947. flex: 1,
  10948. bind: {
  10949. hue: '{selectedColor.h}'
  10950. },
  10951. width: fieldWidth,
  10952. listeners: {
  10953. handledrag: 'onHueSliderHandleDrag'
  10954. }
  10955. },
  10956. {
  10957. xtype: 'numberfield',
  10958. fieldLabel: 'H',
  10959. labelAlign: 'top',
  10960. labelSeparator: '',
  10961. bind: '{hue}',
  10962. hideTrigger: true,
  10963. maxValue: 360,
  10964. minValue: 0,
  10965. allowBlank: false,
  10966. margin: 0
  10967. }
  10968. ]
  10969. };
  10970. },
  10971. // Splits up view declaration for readability
  10972. // Slider and S field
  10973. getSliderAndSField: function(childViewModel) {
  10974. var me = this,
  10975. fieldWidth = me.fieldWidth;
  10976. return {
  10977. xtype: 'container',
  10978. viewModel: childViewModel,
  10979. cls: Ext.baseCSSPrefix + 'colorpicker-escape-overflow',
  10980. width: fieldWidth,
  10981. layout: {
  10982. type: 'vbox',
  10983. align: 'stretch'
  10984. },
  10985. margin: {
  10986. right: me.fieldPad,
  10987. left: me.fieldPad
  10988. },
  10989. items: [
  10990. {
  10991. xtype: 'colorpickerslidersaturation',
  10992. reference: 'satSlider',
  10993. flex: 1,
  10994. bind: {
  10995. saturation: '{saturation}',
  10996. hue: '{selectedColor.h}'
  10997. },
  10998. width: fieldWidth,
  10999. listeners: {
  11000. handledrag: 'onSaturationSliderHandleDrag'
  11001. }
  11002. },
  11003. {
  11004. xtype: 'numberfield',
  11005. fieldLabel: 'S',
  11006. labelAlign: 'top',
  11007. labelSeparator: '',
  11008. bind: '{saturation}',
  11009. hideTrigger: true,
  11010. maxValue: 100,
  11011. minValue: 0,
  11012. allowBlank: false,
  11013. margin: 0
  11014. }
  11015. ]
  11016. };
  11017. },
  11018. // Splits up view declaration for readability
  11019. // Slider and V field
  11020. getSliderAndVField: function(childViewModel) {
  11021. var me = this,
  11022. fieldWidth = me.fieldWidth;
  11023. return {
  11024. xtype: 'container',
  11025. viewModel: childViewModel,
  11026. cls: Ext.baseCSSPrefix + 'colorpicker-escape-overflow',
  11027. width: fieldWidth,
  11028. layout: {
  11029. type: 'vbox',
  11030. align: 'stretch'
  11031. },
  11032. items: [
  11033. {
  11034. xtype: 'colorpickerslidervalue',
  11035. reference: 'valueSlider',
  11036. flex: 1,
  11037. bind: {
  11038. value: '{value}',
  11039. hue: '{selectedColor.h}'
  11040. },
  11041. width: fieldWidth,
  11042. listeners: {
  11043. handledrag: 'onValueSliderHandleDrag'
  11044. }
  11045. },
  11046. {
  11047. xtype: 'numberfield',
  11048. fieldLabel: 'V',
  11049. labelAlign: 'top',
  11050. labelSeparator: '',
  11051. bind: '{value}',
  11052. hideTrigger: true,
  11053. maxValue: 100,
  11054. minValue: 0,
  11055. allowBlank: false,
  11056. margin: 0
  11057. }
  11058. ]
  11059. };
  11060. },
  11061. // Splits up view declaration for readability
  11062. // Slider and A field
  11063. getSliderAndAField: function(childViewModel) {
  11064. var me = this,
  11065. fieldWidth = me.fieldWidth;
  11066. return {
  11067. xtype: 'container',
  11068. viewModel: childViewModel,
  11069. cls: Ext.baseCSSPrefix + 'colorpicker-escape-overflow',
  11070. width: fieldWidth,
  11071. layout: {
  11072. type: 'vbox',
  11073. align: 'stretch'
  11074. },
  11075. margin: {
  11076. left: me.fieldPad
  11077. },
  11078. items: [
  11079. {
  11080. xtype: 'colorpickerslideralpha',
  11081. reference: 'alphaSlider',
  11082. flex: 1,
  11083. bind: {
  11084. alpha: '{alpha}',
  11085. color: {
  11086. bindTo: '{selectedColor}',
  11087. deep: true
  11088. }
  11089. },
  11090. width: fieldWidth,
  11091. listeners: {
  11092. handledrag: 'onAlphaSliderHandleDrag'
  11093. }
  11094. },
  11095. {
  11096. xtype: 'numberfield',
  11097. fieldLabel: 'A',
  11098. labelAlign: 'top',
  11099. labelSeparator: '',
  11100. bind: '{alpha}',
  11101. hideTrigger: true,
  11102. maxValue: 100,
  11103. minValue: 0,
  11104. allowBlank: false,
  11105. margin: 0
  11106. }
  11107. ]
  11108. };
  11109. },
  11110. // Splits up view declaration for readability
  11111. // Preview current/previous color squares and OK and Cancel buttons
  11112. getPreviewAndButtons: function(childViewModel, config) {
  11113. // selected color preview is always shown
  11114. var items = [
  11115. {
  11116. xtype: 'colorpickercolorpreview',
  11117. flex: 1,
  11118. bind: {
  11119. color: {
  11120. bindTo: '{selectedColor}',
  11121. deep: true
  11122. }
  11123. }
  11124. }
  11125. ];
  11126. // previous color preview is optional
  11127. if (config.showPreviousColor) {
  11128. items.push({
  11129. xtype: 'colorpickercolorpreview',
  11130. flex: 1,
  11131. bind: {
  11132. color: {
  11133. bindTo: '{previousColor}',
  11134. deep: true
  11135. }
  11136. },
  11137. listeners: {
  11138. click: 'onPreviousColorSelected'
  11139. }
  11140. });
  11141. }
  11142. // Ok/Cancel buttons are optional
  11143. if (config.showOkCancelButtons) {
  11144. items.push({
  11145. xtype: 'button',
  11146. text: 'OK',
  11147. margin: '10 0 0 0',
  11148. padding: '10 0 10 0',
  11149. handler: 'onOK'
  11150. }, {
  11151. xtype: 'button',
  11152. text: 'Cancel',
  11153. margin: '10 0 0 0',
  11154. padding: '10 0 10 0',
  11155. handler: 'onCancel'
  11156. });
  11157. }
  11158. return {
  11159. xtype: 'container',
  11160. viewModel: childViewModel,
  11161. width: 70,
  11162. margin: '0 0 0 10',
  11163. items: items,
  11164. layout: {
  11165. type: 'vbox',
  11166. align: 'stretch'
  11167. }
  11168. };
  11169. }
  11170. });
  11171. /**
  11172. * @private
  11173. */
  11174. Ext.define('Ext.ux.colorpick.ButtonController', {
  11175. extend: 'Ext.app.ViewController',
  11176. alias: 'controller.colorpick-buttoncontroller',
  11177. requires: [
  11178. 'Ext.window.Window',
  11179. 'Ext.layout.container.Fit',
  11180. 'Ext.ux.colorpick.Selector',
  11181. 'Ext.ux.colorpick.ColorUtils'
  11182. ],
  11183. afterRender: function(view) {
  11184. view.updateColor(view.getColor());
  11185. },
  11186. destroy: function() {
  11187. var view = this.getView(),
  11188. colorPickerWindow = view.colorPickerWindow;
  11189. if (colorPickerWindow) {
  11190. colorPickerWindow.destroy();
  11191. view.colorPickerWindow = view.colorPicker = null;
  11192. }
  11193. this.callParent();
  11194. },
  11195. getPopup: function() {
  11196. var view = this.getView(),
  11197. popup = view.colorPickerWindow,
  11198. selector;
  11199. if (!popup) {
  11200. popup = Ext.create(view.getPopup());
  11201. view.colorPickerWindow = popup;
  11202. popup.colorPicker = view.colorPicker = selector = popup.lookupReference('selector');
  11203. selector.setFormat(view.getFormat());
  11204. selector.on({
  11205. ok: 'onColorPickerOK',
  11206. cancel: 'onColorPickerCancel',
  11207. scope: this
  11208. });
  11209. popup.on({
  11210. close: 'onColorPickerCancel',
  11211. scope: this
  11212. });
  11213. }
  11214. return popup;
  11215. },
  11216. // When button is clicked show the color picker window
  11217. onClick: function() {
  11218. var me = this,
  11219. view = me.getView(),
  11220. color = view.getColor(),
  11221. popup = me.getPopup(),
  11222. colorPicker = popup.colorPicker;
  11223. colorPicker.setColor(color);
  11224. colorPicker.setPreviousColor(color);
  11225. popup.showBy(view, 'tl-br?');
  11226. },
  11227. onColorPickerOK: function(picker) {
  11228. var view = this.getView(),
  11229. color = picker.getColor(),
  11230. cpWin = view.colorPickerWindow;
  11231. cpWin.hide();
  11232. view.setColor(color);
  11233. },
  11234. onColorPickerCancel: function() {
  11235. var view = this.getView(),
  11236. cpWin = view.colorPickerWindow;
  11237. cpWin.hide();
  11238. },
  11239. syncColor: function(color) {
  11240. var view = this.getView();
  11241. Ext.ux.colorpick.ColorUtils.setBackground(view.filterEl, color);
  11242. }
  11243. });
  11244. /**
  11245. * A simple color swatch that can be clicked to bring up the color selector.
  11246. *
  11247. * The selected color is configurable via {@link #value}.
  11248. *
  11249. * @example
  11250. * Ext.create('Ext.ux.colorpick.Button', {
  11251. * value: '993300', // initial selected color
  11252. * renderTo: Ext.getBody(),
  11253. *
  11254. * listeners: {
  11255. * select: function(picker, selColor) {
  11256. * Ext.Msg.alert('Color', selColor);
  11257. * }
  11258. * }
  11259. * });
  11260. */
  11261. Ext.define('Ext.ux.colorpick.Button', {
  11262. extend: 'Ext.Component',
  11263. xtype: 'colorbutton',
  11264. controller: 'colorpick-buttoncontroller',
  11265. mixins: [
  11266. 'Ext.ux.colorpick.Selection'
  11267. ],
  11268. requires: [
  11269. 'Ext.ux.colorpick.ButtonController'
  11270. ],
  11271. baseCls: Ext.baseCSSPrefix + 'colorpicker-button',
  11272. width: 20,
  11273. height: 20,
  11274. childEls: [
  11275. 'btnEl',
  11276. 'filterEl'
  11277. ],
  11278. config: {
  11279. /**
  11280. * @cfg {Object} popup
  11281. * This object configures the popup window and colorselector component displayed
  11282. * when this button is clicked. Applications should not need to configure this.
  11283. * @private
  11284. */
  11285. popup: {
  11286. lazy: true,
  11287. $value: {
  11288. xtype: 'window',
  11289. closeAction: 'hide',
  11290. referenceHolder: true,
  11291. minWidth: 540,
  11292. minHeight: 200,
  11293. layout: 'fit',
  11294. header: false,
  11295. resizable: true,
  11296. items: {
  11297. xtype: 'colorselector',
  11298. reference: 'selector',
  11299. showPreviousColor: true,
  11300. showOkCancelButtons: true
  11301. }
  11302. }
  11303. }
  11304. },
  11305. defaultBindProperty: 'value',
  11306. twoWayBindable: 'value',
  11307. /* eslint-disable max-len */
  11308. // Solve issue with IE, when applying a filter the click listener is not being fired.
  11309. renderTpl: '<div id="{id}-filterEl" data-ref="filterEl" style="height:100%; width:100%; position: absolute;"></div>' + '<a id="{id}-btnEl" data-ref="btnEl" style="height:100%; width:100%; position: absolute;"></a>',
  11310. /* eslint-enable max-len */
  11311. listeners: {
  11312. click: 'onClick',
  11313. element: 'btnEl'
  11314. },
  11315. /**
  11316. * @event change
  11317. * Fires when a color is selected.
  11318. * @param {Ext.ux.colorpick.Selector} this
  11319. * @param {String} color The value of the selected color as per specified {@link #format}.
  11320. * @param {String} previousColor The previous color value.
  11321. */
  11322. updateColor: function(color) {
  11323. var me = this,
  11324. cp = me.colorPicker;
  11325. me.mixins.colorselection.updateColor.call(me, color);
  11326. Ext.ux.colorpick.ColorUtils.setBackground(me.filterEl, color);
  11327. if (cp) {
  11328. cp.setColor(color);
  11329. }
  11330. },
  11331. // Sets this.format and color picker's setFormat()
  11332. updateFormat: function(format) {
  11333. var cp = this.colorPicker;
  11334. if (cp) {
  11335. cp.setFormat(format);
  11336. }
  11337. }
  11338. });
  11339. /**
  11340. * A field that can be clicked to bring up the color picker.
  11341. * The selected color is configurable via {@link #value}.
  11342. *
  11343. * @example
  11344. * Ext.create({
  11345. * xtype: 'colorfield',
  11346. * renderTo: Ext.getBody(),
  11347. *
  11348. * value: '#993300', // initial selected color
  11349. *
  11350. * listeners : {
  11351. * change: function(field, color) {
  11352. * console.log('New color: ' + color);
  11353. * }
  11354. * }
  11355. * });
  11356. */
  11357. Ext.define('Ext.ux.colorpick.Field', {
  11358. extend: 'Ext.form.field.Picker',
  11359. xtype: 'colorfield',
  11360. mixins: [
  11361. 'Ext.ux.colorpick.Selection'
  11362. ],
  11363. requires: [
  11364. 'Ext.window.Window',
  11365. 'Ext.ux.colorpick.Selector',
  11366. 'Ext.ux.colorpick.ColorUtils',
  11367. 'Ext.layout.container.Fit'
  11368. ],
  11369. editable: false,
  11370. matchFieldWidth: false,
  11371. // picker is usually wider than field
  11372. // "Color Swatch" shown on the left of the field
  11373. beforeBodyEl: [
  11374. '<div class="' + Ext.baseCSSPrefix + 'colorpicker-field-swatch">' + '<div id="{id}-swatchEl" data-ref="swatchEl" class="' + Ext.baseCSSPrefix + 'colorpicker-field-swatch-inner"></div>' + '</div>'
  11375. ],
  11376. cls: Ext.baseCSSPrefix + 'colorpicker-field',
  11377. childEls: [
  11378. 'swatchEl'
  11379. ],
  11380. checkChangeEvents: [
  11381. 'change'
  11382. ],
  11383. config: {
  11384. /**
  11385. * @cfg {Object} popup
  11386. * This object configures the popup window and colorselector component displayed
  11387. * when this button is clicked. Applications should not need to configure this.
  11388. * @private
  11389. */
  11390. popup: {
  11391. lazy: true,
  11392. $value: {
  11393. xtype: 'window',
  11394. closeAction: 'hide',
  11395. referenceHolder: true,
  11396. minWidth: 540,
  11397. minHeight: 200,
  11398. layout: 'fit',
  11399. header: false,
  11400. resizable: true,
  11401. items: {
  11402. xtype: 'colorselector',
  11403. reference: 'selector',
  11404. showPreviousColor: true,
  11405. showOkCancelButtons: true
  11406. }
  11407. }
  11408. }
  11409. },
  11410. /**
  11411. * @event change
  11412. * Fires when a color is selected or if the field value is updated (if {@link #editable}).
  11413. * @param {Ext.ux.colorpick.Field} this
  11414. * @param {String} color The value of the selected color as per specified {@link #format}.
  11415. * @param {String} previousColor The previous color value.
  11416. */
  11417. initComponent: function() {
  11418. var me = this;
  11419. me.callParent();
  11420. me.on('change', me.onHexChange);
  11421. },
  11422. // NOTE: Since much of the logic of a picker class is overriding methods from the
  11423. // base class, we don't bother to split out the small remainder as a controller.
  11424. afterRender: function() {
  11425. this.callParent();
  11426. this.updateValue(this.value);
  11427. },
  11428. // override as required by parent pickerfield
  11429. createPicker: function() {
  11430. var me = this,
  11431. popup = me.getPopup(),
  11432. picker;
  11433. // the window will actually be shown and will house the picker
  11434. me.colorPickerWindow = popup = Ext.create(popup);
  11435. me.colorPicker = picker = popup.lookupReference('selector');
  11436. picker.setFormat(me.getFormat());
  11437. picker.setColor(me.getColor());
  11438. picker.setHexReadOnly(!me.editable);
  11439. picker.on({
  11440. ok: 'onColorPickerOK',
  11441. cancel: 'onColorPickerCancel',
  11442. scope: me
  11443. });
  11444. popup.on({
  11445. close: 'onColorPickerCancel',
  11446. scope: me
  11447. });
  11448. return me.colorPickerWindow;
  11449. },
  11450. // When the Ok button is clicked on color picker, preserve the previous value
  11451. onColorPickerOK: function(colorPicker) {
  11452. this.setColor(colorPicker.getColor());
  11453. this.collapse();
  11454. },
  11455. onColorPickerCancel: function() {
  11456. this.collapse();
  11457. },
  11458. onExpand: function() {
  11459. var color = this.getColor();
  11460. this.colorPicker.setPreviousColor(color);
  11461. },
  11462. onHexChange: function(field) {
  11463. if (field.validate()) {
  11464. this.setValue(field.getValue());
  11465. }
  11466. },
  11467. // Expects value formatted as per "format" config
  11468. setValue: function(color) {
  11469. var me = this;
  11470. if (Ext.ux.colorpick.ColorUtils.isValid(color)) {
  11471. color = me.applyValue(color);
  11472. me.callParent([
  11473. color
  11474. ]);
  11475. // always update in case opacity changes, even if value doesn't have it
  11476. // to handle "hex6" non-opacity type of format
  11477. me.updateValue(color);
  11478. }
  11479. },
  11480. // Sets this.format and color picker's setFormat()
  11481. updateFormat: function(format) {
  11482. var cp = this.colorPicker;
  11483. if (cp) {
  11484. cp.setFormat(format);
  11485. }
  11486. },
  11487. updateValue: function(color) {
  11488. var me = this,
  11489. c;
  11490. // If the "value" is changed, update "color" as well. Since these are always
  11491. // tracking each other, we guard against the case where we are being updated
  11492. // *because* "color" is being set.
  11493. if (!me.syncing) {
  11494. me.syncing = true;
  11495. me.setColor(color);
  11496. me.syncing = false;
  11497. }
  11498. c = me.getColor();
  11499. if (c) {
  11500. Ext.ux.colorpick.ColorUtils.setBackground(me.swatchEl, c);
  11501. if (me.colorPicker) {
  11502. me.colorPicker.setColor(c);
  11503. }
  11504. }
  11505. },
  11506. validator: function(val) {
  11507. if (!Ext.ux.colorpick.ColorUtils.isValid(val)) {
  11508. return this.invalidText;
  11509. }
  11510. return true;
  11511. }
  11512. });
  11513. /**
  11514. * Paging Memory Proxy, allows to use paging grid with in memory dataset
  11515. */
  11516. Ext.define('Ext.ux.data.PagingMemoryProxy', {
  11517. extend: 'Ext.data.proxy.Memory',
  11518. alias: 'proxy.pagingmemory',
  11519. alternateClassName: 'Ext.data.PagingMemoryProxy',
  11520. constructor: function() {
  11521. Ext.log.warn('Ext.ux.data.PagingMemoryProxy functionality has been merged ' + 'into Ext.data.proxy.Memory by using the enablePaging flag.');
  11522. this.callParent(arguments);
  11523. },
  11524. read: function(operation, callback, scope) {
  11525. var reader = this.getReader(),
  11526. result = reader.read(this.data),
  11527. sorters, filters, sorterFn, records;
  11528. scope = scope || this;
  11529. // filtering
  11530. filters = operation.filters;
  11531. if (filters.length > 0) {
  11532. // at this point we have an array of Ext.util.Filter objects to filter with,
  11533. // so here we construct a function that combines these filters by ANDing them together
  11534. records = [];
  11535. Ext.each(result.records, function(record) {
  11536. var isMatch = true,
  11537. length = filters.length,
  11538. filter, fn, scope, i;
  11539. for (i = 0; i < length; i++) {
  11540. filter = filters[i];
  11541. fn = filter.filterFn;
  11542. scope = filter.scope;
  11543. isMatch = isMatch && fn.call(scope, record);
  11544. }
  11545. if (isMatch) {
  11546. records.push(record);
  11547. }
  11548. }, this);
  11549. result.records = records;
  11550. result.totalRecords = result.total = records.length;
  11551. }
  11552. // sorting
  11553. sorters = operation.sorters;
  11554. if (sorters.length > 0) {
  11555. // construct an amalgamated sorter function which combines all of the Sorters passed
  11556. sorterFn = function(r1, r2) {
  11557. var result = sorters[0].sort(r1, r2),
  11558. length = sorters.length,
  11559. i;
  11560. // if we have more than one sorter, OR any additional sorter functions together
  11561. for (i = 1; i < length; i++) {
  11562. result = result || sorters[i].sort.call(this, r1, r2);
  11563. }
  11564. return result;
  11565. };
  11566. result.records.sort(sorterFn);
  11567. }
  11568. // paging (use undefined cause start can also be 0 (thus false))
  11569. if (operation.start !== undefined && operation.limit !== undefined) {
  11570. result.records = result.records.slice(operation.start, operation.start + operation.limit);
  11571. result.count = result.records.length;
  11572. }
  11573. Ext.apply(operation, {
  11574. resultSet: result
  11575. });
  11576. operation.setCompleted();
  11577. operation.setSuccessful();
  11578. Ext.defer(function() {
  11579. Ext.callback(callback, scope, [
  11580. operation
  11581. ]);
  11582. }, 10);
  11583. }
  11584. });
  11585. /**
  11586. * This class is used as a grid `plugin`. It provides a DropZone which cooperates with
  11587. * DragZones whose dragData contains a "field" property representing a form Field.
  11588. * Fields may be dropped onto grid data cells containing a matching data type.
  11589. */
  11590. Ext.define('Ext.ux.dd.CellFieldDropZone', {
  11591. /* eslint-disable vars-on-top */
  11592. extend: 'Ext.dd.DropZone',
  11593. alias: 'plugin.ux-cellfielddropzone',
  11594. containerScroll: true,
  11595. /**
  11596. * @cfg {Function/String} onCellDrop
  11597. * The function to call on a cell data drop, or the name of the function on the
  11598. * corresponding `{@link Ext.app.ViewController controller}`. For details on the
  11599. * parameters, see `{@link #method!onCellDrop onCellDrop}`.
  11600. */
  11601. /**
  11602. * This method is called when a field is dropped on a cell. This method is normally
  11603. * replaced by the `{@link #cfg!onCellDrop onCellDrop}` config property passed to the
  11604. * constructor.
  11605. * @param {String} fieldName The name of the field.
  11606. * @param {Mixed} value The value of the field.
  11607. * @method onCellDrop
  11608. */
  11609. onCellDrop: Ext.emptyFn,
  11610. constructor: function(cfg) {
  11611. if (cfg) {
  11612. var me = this,
  11613. ddGroup = cfg.ddGroup,
  11614. onCellDrop = cfg.onCellDrop;
  11615. if (onCellDrop) {
  11616. if (typeof onCellDrop === 'string') {
  11617. me.onCellDropFn = onCellDrop;
  11618. me.onCellDrop = me.callCellDrop;
  11619. } else {
  11620. me.onCellDrop = onCellDrop;
  11621. }
  11622. }
  11623. if (ddGroup) {
  11624. me.ddGroup = ddGroup;
  11625. }
  11626. }
  11627. },
  11628. init: function(grid) {
  11629. var me = this;
  11630. // Call the DropZone constructor using the View's scrolling element
  11631. // only after the grid has been rendered.
  11632. if (grid.rendered) {
  11633. me.grid = grid;
  11634. grid.getView().on({
  11635. render: function(v) {
  11636. me.view = v;
  11637. Ext.ux.dd.CellFieldDropZone.superclass.constructor.call(me, me.view.el);
  11638. },
  11639. single: true
  11640. });
  11641. } else {
  11642. grid.on('render', me.init, me, {
  11643. single: true
  11644. });
  11645. }
  11646. },
  11647. getTargetFromEvent: function(e) {
  11648. var me = this,
  11649. v = me.view,
  11650. // Ascertain whether the mousemove is within a grid cell
  11651. cell = e.getTarget(v.getCellSelector());
  11652. if (cell) {
  11653. // We *are* within a grid cell, so ask the View exactly which one,
  11654. // Extract data from the Model to create a target object for
  11655. // processing in subsequent onNodeXXXX methods. Note that the target does
  11656. // not have to be a DOM element. It can be whatever the noNodeXXX methods are
  11657. // programmed to expect.
  11658. var row = v.findItemByChild(cell),
  11659. columnIndex = cell.cellIndex;
  11660. if (row && Ext.isDefined(columnIndex)) {
  11661. return {
  11662. node: cell,
  11663. record: v.getRecord(row),
  11664. fieldName: me.grid.getVisibleColumnManager().getColumns()[columnIndex].dataIndex
  11665. };
  11666. }
  11667. }
  11668. },
  11669. onNodeEnter: function(target, dd, e, dragData) {
  11670. // On Node enter, see if it is valid for us to drop the field on that type of
  11671. // column.
  11672. delete this.dropOK;
  11673. if (!target) {
  11674. return;
  11675. }
  11676. // Check that a field is being dragged.
  11677. var f = dragData.field;
  11678. if (!f) {
  11679. return;
  11680. }
  11681. // Check whether the data type of the column being dropped on accepts the
  11682. // dragged field type. If so, set dropOK flag, and highlight the target node.
  11683. var field = target.record.fieldsMap[target.fieldName];
  11684. if (field.isNumeric) {
  11685. if (!f.isXType('numberfield')) {
  11686. return;
  11687. }
  11688. } else if (field.isDateField) {
  11689. if (!f.isXType('datefield')) {
  11690. return;
  11691. }
  11692. } else if (field.isBooleanField) {
  11693. if (!f.isXType('checkbox')) {
  11694. return;
  11695. }
  11696. }
  11697. this.dropOK = true;
  11698. Ext.fly(target.node).addCls('x-drop-target-active');
  11699. },
  11700. onNodeOver: function(target, dd, e, dragData) {
  11701. // Return the class name to add to the drag proxy. This provides a visual
  11702. // indication of drop allowed or not allowed.
  11703. return this.dropOK ? this.dropAllowed : this.dropNotAllowed;
  11704. },
  11705. onNodeOut: function(target, dd, e, dragData) {
  11706. Ext.fly(target.node).removeCls('x-drop-target-active');
  11707. },
  11708. onNodeDrop: function(target, dd, e, dragData) {
  11709. // Process the drop event if we have previously ascertained that a drop is OK.
  11710. if (this.dropOK) {
  11711. var value = dragData.field.getValue();
  11712. target.record.set(target.fieldName, value);
  11713. this.onCellDrop(target.fieldName, value);
  11714. return true;
  11715. }
  11716. },
  11717. callCellDrop: function(fieldName, value) {
  11718. Ext.callback(this.onCellDropFn, null, [
  11719. fieldName,
  11720. value
  11721. ], 0, this.grid);
  11722. }
  11723. });
  11724. Ext.define('Ext.ux.dd.PanelFieldDragZone', {
  11725. extend: 'Ext.dd.DragZone',
  11726. alias: 'plugin.ux-panelfielddragzone',
  11727. scroll: false,
  11728. constructor: function(cfg) {
  11729. if (cfg) {
  11730. if (cfg.ddGroup) {
  11731. this.ddGroup = cfg.ddGroup;
  11732. }
  11733. }
  11734. },
  11735. init: function(panel) {
  11736. var el;
  11737. // Call the DragZone's constructor. The Panel must have been rendered.
  11738. // Panel is an HtmlElement
  11739. if (panel.nodeType) {
  11740. // Called via dragzone::init
  11741. Ext.ux.dd.PanelFieldDragZone.superclass.init.apply(this, arguments);
  11742. } else // Panel is a Component - need the el
  11743. {
  11744. // Called via plugin::init
  11745. if (panel.rendered) {
  11746. el = panel.getEl();
  11747. el.unselectable();
  11748. Ext.ux.dd.PanelFieldDragZone.superclass.constructor.call(this, el);
  11749. } else {
  11750. panel.on('afterrender', this.init, this, {
  11751. single: true
  11752. });
  11753. }
  11754. }
  11755. },
  11756. getDragData: function(e) {
  11757. // On mousedown, we ascertain whether it is on one of our draggable Fields.
  11758. // If so, we collect data about the draggable object, and return a drag data
  11759. // object which contains our own data, plus a "ddel" property which is a DOM
  11760. // node which provides a "view" of the dragged data.
  11761. var targetLabel = e.getTarget('label', null, true),
  11762. text, oldMark, field, dragEl;
  11763. if (targetLabel) {
  11764. // Get the data we are dragging: the Field
  11765. // create a ddel for the drag proxy to display
  11766. field = Ext.getCmp(targetLabel.up('.' + Ext.form.Labelable.prototype.formItemCls).id);
  11767. // Temporary prevent marking the field as invalid, since it causes changes
  11768. // to the underlying dom element which can cause problems in IE
  11769. oldMark = field.preventMark;
  11770. field.preventMark = true;
  11771. if (field.isValid()) {
  11772. field.preventMark = oldMark;
  11773. dragEl = document.createElement('div');
  11774. dragEl.className = Ext.baseCSSPrefix + 'form-text';
  11775. text = field.getRawValue();
  11776. dragEl.innerHTML = Ext.isEmpty(text) ? '&#160;' : text;
  11777. Ext.fly(dragEl).setWidth(field.getEl().getWidth());
  11778. return {
  11779. field: field,
  11780. ddel: dragEl
  11781. };
  11782. }
  11783. e.stopEvent();
  11784. field.preventMark = oldMark;
  11785. }
  11786. },
  11787. getRepairXY: function() {
  11788. // The coordinates to slide the drag proxy back to on failed drop.
  11789. return this.dragData.field.getEl().getXY();
  11790. }
  11791. });
  11792. /**
  11793. * Ext JS Library
  11794. * Copyright(c) 2006-2014 Sencha Inc.
  11795. * licensing@sencha.com
  11796. * http://www.sencha.com/license
  11797. *
  11798. * @class Ext.ux.desktop.Desktop
  11799. * @extends Ext.panel.Panel
  11800. * <p>This class manages the wallpaper, shortcuts and taskbar.</p>
  11801. */
  11802. Ext.define('Ext.ux.desktop.Desktop', {
  11803. extend: 'Ext.panel.Panel',
  11804. alias: 'widget.desktop',
  11805. uses: [
  11806. 'Ext.util.MixedCollection',
  11807. 'Ext.menu.Menu',
  11808. 'Ext.view.View',
  11809. // dataview
  11810. 'Ext.window.Window',
  11811. 'Ext.ux.desktop.TaskBar',
  11812. 'Ext.ux.desktop.Wallpaper'
  11813. ],
  11814. activeWindowCls: 'ux-desktop-active-win',
  11815. inactiveWindowCls: 'ux-desktop-inactive-win',
  11816. lastActiveWindow: null,
  11817. border: false,
  11818. html: '&#160;',
  11819. layout: 'fit',
  11820. xTickSize: 1,
  11821. yTickSize: 1,
  11822. app: null,
  11823. /**
  11824. * @cfg {Array/Ext.data.Store} shortcuts
  11825. * The items to add to the DataView. This can be a {@link Ext.data.Store Store} or a
  11826. * simple array. Items should minimally provide the fields in the
  11827. * {@link Ext.ux.desktop.ShortcutModel Shortcut}.
  11828. */
  11829. shortcuts: null,
  11830. /**
  11831. * @cfg {String} shortcutItemSelector
  11832. * This property is passed to the DataView for the desktop to select shortcut items.
  11833. * If the {@link #shortcutTpl} is modified, this will probably need to be modified as
  11834. * well.
  11835. */
  11836. shortcutItemSelector: 'div.ux-desktop-shortcut',
  11837. /**
  11838. * @cfg {String} shortcutTpl
  11839. * This XTemplate is used to render items in the DataView. If this is changed, the
  11840. * {@link #shortcutItemSelector} will probably also need to changed.
  11841. */
  11842. /* eslint-disable indent */
  11843. shortcutTpl: [
  11844. '<tpl for=".">',
  11845. '<div class="ux-desktop-shortcut" id="{name}-shortcut">',
  11846. '<div class="ux-desktop-shortcut-icon {iconCls}">',
  11847. '<img src="',
  11848. Ext.BLANK_IMAGE_URL,
  11849. '" title="{name}">',
  11850. '</div>',
  11851. '<span class="ux-desktop-shortcut-text">{name}</span>',
  11852. '</div>',
  11853. '</tpl>',
  11854. '<div class="x-clear"></div>'
  11855. ],
  11856. /* eslint-enable indent */
  11857. /**
  11858. * @cfg {Object} taskbarConfig
  11859. * The config object for the TaskBar.
  11860. */
  11861. taskbarConfig: null,
  11862. windowMenu: null,
  11863. initComponent: function() {
  11864. var me = this,
  11865. wallpaper;
  11866. me.windowMenu = new Ext.menu.Menu(me.createWindowMenu());
  11867. me.bbar = me.taskbar = new Ext.ux.desktop.TaskBar(me.taskbarConfig);
  11868. me.taskbar.windowMenu = me.windowMenu;
  11869. me.windows = new Ext.util.MixedCollection();
  11870. me.contextMenu = new Ext.menu.Menu(me.createDesktopMenu());
  11871. me.items = [
  11872. {
  11873. xtype: 'wallpaper',
  11874. id: me.id + '_wallpaper'
  11875. },
  11876. me.createDataView()
  11877. ];
  11878. me.callParent();
  11879. me.shortcutsView = me.items.getAt(1);
  11880. me.shortcutsView.on('itemclick', me.onShortcutItemClick, me);
  11881. wallpaper = me.wallpaper;
  11882. me.wallpaper = me.items.getAt(0);
  11883. if (wallpaper) {
  11884. me.setWallpaper(wallpaper, me.wallpaperStretch);
  11885. }
  11886. },
  11887. afterRender: function() {
  11888. var me = this;
  11889. me.callParent();
  11890. me.el.on('contextmenu', me.onDesktopMenu, me);
  11891. },
  11892. //------------------------------------------------------
  11893. // Overrideable configuration creation methods
  11894. createDataView: function() {
  11895. var me = this;
  11896. return {
  11897. xtype: 'dataview',
  11898. overItemCls: 'x-view-over',
  11899. trackOver: true,
  11900. itemSelector: me.shortcutItemSelector,
  11901. store: me.shortcuts,
  11902. style: {
  11903. position: 'absolute'
  11904. },
  11905. x: 0,
  11906. y: 0,
  11907. tpl: new Ext.XTemplate(me.shortcutTpl)
  11908. };
  11909. },
  11910. createDesktopMenu: function() {
  11911. var me = this,
  11912. ret = {
  11913. items: me.contextMenuItems || []
  11914. };
  11915. if (ret.items.length) {
  11916. ret.items.push('-');
  11917. }
  11918. ret.items.push({
  11919. text: 'Tile',
  11920. handler: me.tileWindows,
  11921. scope: me,
  11922. minWindows: 1
  11923. }, {
  11924. text: 'Cascade',
  11925. handler: me.cascadeWindows,
  11926. scope: me,
  11927. minWindows: 1
  11928. });
  11929. return ret;
  11930. },
  11931. createWindowMenu: function() {
  11932. var me = this;
  11933. return {
  11934. defaultAlign: 'br-tr',
  11935. items: [
  11936. {
  11937. text: 'Restore',
  11938. handler: me.onWindowMenuRestore,
  11939. scope: me
  11940. },
  11941. {
  11942. text: 'Minimize',
  11943. handler: me.onWindowMenuMinimize,
  11944. scope: me
  11945. },
  11946. {
  11947. text: 'Maximize',
  11948. handler: me.onWindowMenuMaximize,
  11949. scope: me
  11950. },
  11951. '-',
  11952. {
  11953. text: 'Close',
  11954. handler: me.onWindowMenuClose,
  11955. scope: me
  11956. }
  11957. ],
  11958. listeners: {
  11959. beforeshow: me.onWindowMenuBeforeShow,
  11960. hide: me.onWindowMenuHide,
  11961. scope: me
  11962. }
  11963. };
  11964. },
  11965. //------------------------------------------------------
  11966. // Event handler methods
  11967. onDesktopMenu: function(e) {
  11968. var me = this,
  11969. menu = me.contextMenu;
  11970. e.stopEvent();
  11971. if (!menu.rendered) {
  11972. menu.on('beforeshow', me.onDesktopMenuBeforeShow, me);
  11973. }
  11974. menu.showAt(e.getXY());
  11975. menu.doConstrain();
  11976. },
  11977. onDesktopMenuBeforeShow: function(menu) {
  11978. var me = this,
  11979. count = me.windows.getCount();
  11980. menu.items.each(function(item) {
  11981. var min = item.minWindows || 0;
  11982. item.setDisabled(count < min);
  11983. });
  11984. },
  11985. onShortcutItemClick: function(dataView, record) {
  11986. var me = this,
  11987. module = me.app.getModule(record.data.module),
  11988. win = module && module.createWindow();
  11989. if (win) {
  11990. me.restoreWindow(win);
  11991. }
  11992. },
  11993. onWindowClose: function(win) {
  11994. var me = this;
  11995. me.windows.remove(win);
  11996. me.taskbar.removeTaskButton(win.taskButton);
  11997. me.updateActiveWindow();
  11998. },
  11999. //------------------------------------------------------
  12000. // Window context menu handlers
  12001. onWindowMenuBeforeShow: function(menu) {
  12002. var items = menu.items.items,
  12003. win = menu.theWin;
  12004. items[0].setDisabled(win.maximized !== true && win.hidden !== true);
  12005. // Restore
  12006. items[1].setDisabled(win.minimized === true);
  12007. // Minimize
  12008. items[2].setDisabled(win.maximized === true || win.hidden === true);
  12009. },
  12010. // Maximize
  12011. onWindowMenuClose: function() {
  12012. var me = this,
  12013. win = me.windowMenu.theWin;
  12014. win.close();
  12015. },
  12016. onWindowMenuHide: function(menu) {
  12017. Ext.defer(function() {
  12018. menu.theWin = null;
  12019. }, 1);
  12020. },
  12021. onWindowMenuMaximize: function() {
  12022. var me = this,
  12023. win = me.windowMenu.theWin;
  12024. win.maximize();
  12025. win.toFront();
  12026. },
  12027. onWindowMenuMinimize: function() {
  12028. var me = this,
  12029. win = me.windowMenu.theWin;
  12030. win.minimize();
  12031. },
  12032. onWindowMenuRestore: function() {
  12033. var me = this,
  12034. win = me.windowMenu.theWin;
  12035. me.restoreWindow(win);
  12036. },
  12037. //------------------------------------------------------
  12038. // Dynamic (re)configuration methods
  12039. getWallpaper: function() {
  12040. return this.wallpaper.wallpaper;
  12041. },
  12042. setTickSize: function(xTickSize, yTickSize) {
  12043. var me = this,
  12044. xt = me.xTickSize = xTickSize,
  12045. yt = me.yTickSize = (arguments.length > 1) ? yTickSize : xt;
  12046. me.windows.each(function(win) {
  12047. var dd = win.dd,
  12048. resizer = win.resizer;
  12049. dd.xTickSize = xt;
  12050. dd.yTickSize = yt;
  12051. resizer.widthIncrement = xt;
  12052. resizer.heightIncrement = yt;
  12053. });
  12054. },
  12055. setWallpaper: function(wallpaper, stretch) {
  12056. this.wallpaper.setWallpaper(wallpaper, stretch);
  12057. return this;
  12058. },
  12059. //------------------------------------------------------
  12060. // Window management methods
  12061. cascadeWindows: function() {
  12062. var x = 0,
  12063. y = 0,
  12064. zmgr = this.getDesktopZIndexManager();
  12065. zmgr.eachBottomUp(function(win) {
  12066. if (win.isWindow && win.isVisible() && !win.maximized) {
  12067. win.setPosition(x, y);
  12068. x += 20;
  12069. y += 20;
  12070. }
  12071. });
  12072. },
  12073. createWindow: function(config, cls) {
  12074. var me = this,
  12075. win,
  12076. cfg = Ext.applyIf(config || {}, {
  12077. stateful: false,
  12078. isWindow: true,
  12079. constrainHeader: true,
  12080. minimizable: true,
  12081. maximizable: true
  12082. });
  12083. cls = cls || Ext.window.Window;
  12084. win = me.add(new cls(cfg));
  12085. me.windows.add(win);
  12086. win.taskButton = me.taskbar.addTaskButton(win);
  12087. win.animateTarget = win.taskButton.el;
  12088. win.on({
  12089. activate: me.updateActiveWindow,
  12090. beforeshow: me.updateActiveWindow,
  12091. deactivate: me.updateActiveWindow,
  12092. minimize: me.minimizeWindow,
  12093. destroy: me.onWindowClose,
  12094. scope: me
  12095. });
  12096. win.on({
  12097. boxready: function() {
  12098. win.dd.xTickSize = me.xTickSize;
  12099. win.dd.yTickSize = me.yTickSize;
  12100. if (win.resizer) {
  12101. win.resizer.widthIncrement = me.xTickSize;
  12102. win.resizer.heightIncrement = me.yTickSize;
  12103. }
  12104. },
  12105. single: true
  12106. });
  12107. // replace normal window close w/fadeOut animation:
  12108. win.doClose = function() {
  12109. win.doClose = Ext.emptyFn;
  12110. // dblclick can call again...
  12111. win.el.disableShadow();
  12112. win.el.fadeOut({
  12113. listeners: {
  12114. afteranimate: function() {
  12115. win.destroy();
  12116. }
  12117. }
  12118. });
  12119. };
  12120. return win;
  12121. },
  12122. getActiveWindow: function() {
  12123. var win = null,
  12124. zmgr = this.getDesktopZIndexManager();
  12125. if (zmgr) {
  12126. // We cannot rely on activate/deactive because that fires against non-Window
  12127. // components in the stack.
  12128. zmgr.eachTopDown(function(comp) {
  12129. if (comp.isWindow && !comp.hidden) {
  12130. win = comp;
  12131. return false;
  12132. }
  12133. return true;
  12134. });
  12135. }
  12136. return win;
  12137. },
  12138. getDesktopZIndexManager: function() {
  12139. var windows = this.windows;
  12140. // TODO - there has to be a better way to get this...
  12141. return (windows.getCount() && windows.getAt(0).zIndexManager) || null;
  12142. },
  12143. getWindow: function(id) {
  12144. return this.windows.get(id);
  12145. },
  12146. minimizeWindow: function(win) {
  12147. win.minimized = true;
  12148. win.hide();
  12149. },
  12150. restoreWindow: function(win) {
  12151. if (win.isVisible()) {
  12152. win.restore();
  12153. win.toFront();
  12154. } else {
  12155. win.show();
  12156. }
  12157. return win;
  12158. },
  12159. tileWindows: function() {
  12160. var me = this,
  12161. availWidth = me.body.getWidth(true),
  12162. x = me.xTickSize,
  12163. y = me.yTickSize,
  12164. nextY = y;
  12165. me.windows.each(function(win) {
  12166. var w;
  12167. if (win.isVisible() && !win.maximized) {
  12168. w = win.el.getWidth();
  12169. // Wrap to next row if we are not at the line start and this Window will
  12170. // go off the end
  12171. if (x > me.xTickSize && x + w > availWidth) {
  12172. x = me.xTickSize;
  12173. y = nextY;
  12174. }
  12175. win.setPosition(x, y);
  12176. x += w + me.xTickSize;
  12177. nextY = Math.max(nextY, y + win.el.getHeight() + me.yTickSize);
  12178. }
  12179. });
  12180. },
  12181. updateActiveWindow: function() {
  12182. var me = this,
  12183. activeWindow = me.getActiveWindow(),
  12184. last = me.lastActiveWindow;
  12185. if (last && last.destroyed) {
  12186. me.lastActiveWindow = null;
  12187. return;
  12188. }
  12189. if (activeWindow === last) {
  12190. return;
  12191. }
  12192. if (last) {
  12193. if (last.el.dom) {
  12194. last.addCls(me.inactiveWindowCls);
  12195. last.removeCls(me.activeWindowCls);
  12196. }
  12197. last.active = false;
  12198. }
  12199. me.lastActiveWindow = activeWindow;
  12200. if (activeWindow) {
  12201. activeWindow.addCls(me.activeWindowCls);
  12202. activeWindow.removeCls(me.inactiveWindowCls);
  12203. activeWindow.minimized = false;
  12204. activeWindow.active = true;
  12205. }
  12206. me.taskbar.setActiveButton(activeWindow && activeWindow.taskButton);
  12207. }
  12208. });
  12209. /**
  12210. * Ext JS Library
  12211. * Copyright(c) 2006-2014 Sencha Inc.
  12212. * licensing@sencha.com
  12213. * http://www.sencha.com/license
  12214. * @class Ext.ux.desktop.App
  12215. */
  12216. Ext.define('Ext.ux.desktop.App', {
  12217. mixins: {
  12218. observable: 'Ext.util.Observable'
  12219. },
  12220. requires: [
  12221. 'Ext.container.Viewport',
  12222. 'Ext.ux.desktop.Desktop'
  12223. ],
  12224. isReady: false,
  12225. modules: null,
  12226. useQuickTips: true,
  12227. constructor: function(config) {
  12228. var me = this;
  12229. me.mixins.observable.constructor.call(this, config);
  12230. if (Ext.isReady) {
  12231. Ext.defer(me.init, 10, me);
  12232. } else {
  12233. Ext.onReady(me.init, me);
  12234. }
  12235. },
  12236. init: function() {
  12237. var me = this,
  12238. desktopCfg;
  12239. if (me.useQuickTips) {
  12240. Ext.QuickTips.init();
  12241. }
  12242. me.modules = me.getModules();
  12243. if (me.modules) {
  12244. me.initModules(me.modules);
  12245. }
  12246. desktopCfg = me.getDesktopConfig();
  12247. me.desktop = new Ext.ux.desktop.Desktop(desktopCfg);
  12248. me.viewport = new Ext.container.Viewport({
  12249. layout: 'fit',
  12250. items: [
  12251. me.desktop
  12252. ]
  12253. });
  12254. Ext.getWin().on('beforeunload', me.onUnload, me);
  12255. me.isReady = true;
  12256. me.fireEvent('ready', me);
  12257. },
  12258. /**
  12259. * This method returns the configuration object for the Desktop object. A derived
  12260. * class can override this method, call the base version to build the config and
  12261. * then modify the returned object before returning it.
  12262. */
  12263. getDesktopConfig: function() {
  12264. var me = this,
  12265. cfg = {
  12266. app: me,
  12267. taskbarConfig: me.getTaskbarConfig()
  12268. };
  12269. Ext.apply(cfg, me.desktopConfig);
  12270. return cfg;
  12271. },
  12272. getModules: Ext.emptyFn,
  12273. /**
  12274. * This method returns the configuration object for the Start Button. A derived
  12275. * class can override this method, call the base version to build the config and
  12276. * then modify the returned object before returning it.
  12277. */
  12278. getStartConfig: function() {
  12279. var me = this,
  12280. cfg = {
  12281. app: me,
  12282. menu: []
  12283. },
  12284. launcher;
  12285. Ext.apply(cfg, me.startConfig);
  12286. Ext.each(me.modules, function(module) {
  12287. launcher = module.launcher;
  12288. if (launcher) {
  12289. launcher.handler = launcher.handler || Ext.bind(me.createWindow, me, [
  12290. module
  12291. ]);
  12292. cfg.menu.push(module.launcher);
  12293. }
  12294. });
  12295. return cfg;
  12296. },
  12297. createWindow: function(module) {
  12298. var window = module.createWindow();
  12299. window.show();
  12300. },
  12301. /**
  12302. * This method returns the configuration object for the TaskBar. A derived class
  12303. * can override this method, call the base version to build the config and then
  12304. * modify the returned object before returning it.
  12305. */
  12306. getTaskbarConfig: function() {
  12307. var me = this,
  12308. cfg = {
  12309. app: me,
  12310. startConfig: me.getStartConfig()
  12311. };
  12312. Ext.apply(cfg, me.taskbarConfig);
  12313. return cfg;
  12314. },
  12315. initModules: function(modules) {
  12316. var me = this;
  12317. Ext.each(modules, function(module) {
  12318. module.app = me;
  12319. });
  12320. },
  12321. getModule: function(name) {
  12322. var ms = this.modules,
  12323. i, len, m;
  12324. for (i = 0 , len = ms.length; i < len; i++) {
  12325. m = ms[i];
  12326. // eslint-disable-next-line eqeqeq
  12327. if (m.id == name || m.appType == name) {
  12328. return m;
  12329. }
  12330. }
  12331. return null;
  12332. },
  12333. onReady: function(fn, scope) {
  12334. if (this.isReady) {
  12335. fn.call(scope, this);
  12336. } else {
  12337. this.on({
  12338. ready: fn,
  12339. scope: scope,
  12340. single: true
  12341. });
  12342. }
  12343. },
  12344. getDesktop: function() {
  12345. return this.desktop;
  12346. },
  12347. onUnload: function(e) {
  12348. if (this.fireEvent('beforeunload', this) === false) {
  12349. e.stopEvent();
  12350. }
  12351. }
  12352. });
  12353. /*
  12354. * Ext JS Library
  12355. * Copyright(c) 2006-2014 Sencha Inc.
  12356. * licensing@sencha.com
  12357. * http://www.sencha.com/license
  12358. */
  12359. Ext.define('Ext.ux.desktop.Module', {
  12360. mixins: {
  12361. observable: 'Ext.util.Observable'
  12362. },
  12363. constructor: function(config) {
  12364. this.mixins.observable.constructor.call(this, config);
  12365. this.init();
  12366. },
  12367. init: Ext.emptyFn
  12368. });
  12369. /*
  12370. * Ext JS Library
  12371. * Copyright(c) 2006-2014 Sencha Inc.
  12372. * licensing@sencha.com
  12373. * http://www.sencha.com/license
  12374. */
  12375. /**
  12376. * @class Ext.ux.desktop.ShortcutModel
  12377. * @extends Ext.data.Model
  12378. * This model defines the minimal set of fields for desktop shortcuts.
  12379. */
  12380. Ext.define('Ext.ux.desktop.ShortcutModel', {
  12381. extend: 'Ext.data.Model',
  12382. fields: [
  12383. {
  12384. name: 'name',
  12385. convert: Ext.String.createVarName
  12386. },
  12387. {
  12388. name: 'iconCls'
  12389. },
  12390. {
  12391. name: 'module'
  12392. }
  12393. ]
  12394. });
  12395. /**
  12396. * Ext JS Library
  12397. * Copyright(c) 2006-2014 Sencha Inc.
  12398. * licensing@sencha.com
  12399. * http://www.sencha.com/license
  12400. * @class Ext.ux.desktop.StartMenu
  12401. */
  12402. Ext.define('Ext.ux.desktop.StartMenu', {
  12403. extend: 'Ext.menu.Menu',
  12404. // We want header styling like a Panel
  12405. baseCls: Ext.baseCSSPrefix + 'panel',
  12406. // Special styling within
  12407. cls: 'x-menu ux-start-menu',
  12408. bodyCls: 'ux-start-menu-body',
  12409. defaultAlign: 'bl-tl',
  12410. iconCls: 'user',
  12411. bodyBorder: true,
  12412. width: 300,
  12413. initComponent: function() {
  12414. var me = this;
  12415. me.layout.align = 'stretch';
  12416. me.items = me.menu;
  12417. me.callParent();
  12418. me.toolbar = new Ext.toolbar.Toolbar(Ext.apply({
  12419. dock: 'right',
  12420. cls: 'ux-start-menu-toolbar',
  12421. vertical: true,
  12422. width: 100,
  12423. layout: {
  12424. align: 'stretch'
  12425. }
  12426. }, me.toolConfig));
  12427. me.addDocked(me.toolbar);
  12428. delete me.toolItems;
  12429. },
  12430. addMenuItem: function() {
  12431. var cmp = this.menu;
  12432. cmp.add.apply(cmp, arguments);
  12433. },
  12434. addToolItem: function() {
  12435. var cmp = this.toolbar;
  12436. cmp.add.apply(cmp, arguments);
  12437. }
  12438. });
  12439. // StartMenu
  12440. /*
  12441. * Ext JS Library
  12442. * Copyright(c) 2006-2014 Sencha Inc.
  12443. * licensing@sencha.com
  12444. * http://www.sencha.com/license
  12445. */
  12446. /**
  12447. * @class Ext.ux.desktop.TaskBar
  12448. * @extends Ext.toolbar.Toolbar
  12449. */
  12450. Ext.define('Ext.ux.desktop.TaskBar', {
  12451. // This must be a toolbar. we rely on acquired toolbar classes and inherited toolbar methods
  12452. // for our child items to instantiate and render correctly.
  12453. extend: 'Ext.toolbar.Toolbar',
  12454. requires: [
  12455. 'Ext.button.Button',
  12456. 'Ext.resizer.Splitter',
  12457. 'Ext.menu.Menu',
  12458. 'Ext.ux.desktop.StartMenu'
  12459. ],
  12460. alias: 'widget.taskbar',
  12461. cls: 'ux-taskbar',
  12462. /**
  12463. * @cfg {String} startBtnText
  12464. * The text for the Start Button.
  12465. */
  12466. startBtnText: 'Start',
  12467. initComponent: function() {
  12468. var me = this;
  12469. me.startMenu = new Ext.ux.desktop.StartMenu(me.startConfig);
  12470. me.quickStart = new Ext.toolbar.Toolbar(me.getQuickStart());
  12471. me.windowBar = new Ext.toolbar.Toolbar(me.getWindowBarConfig());
  12472. me.tray = new Ext.toolbar.Toolbar(me.getTrayConfig());
  12473. me.items = [
  12474. {
  12475. xtype: 'button',
  12476. cls: 'ux-start-button',
  12477. iconCls: 'ux-start-button-icon',
  12478. menu: me.startMenu,
  12479. menuAlign: 'bl-tl',
  12480. text: me.startBtnText
  12481. },
  12482. me.quickStart,
  12483. {
  12484. xtype: 'splitter',
  12485. html: '&#160;',
  12486. height: 14,
  12487. width: 2,
  12488. // TODO - there should be a CSS way here
  12489. cls: 'x-toolbar-separator x-toolbar-separator-horizontal'
  12490. },
  12491. me.windowBar,
  12492. '-',
  12493. me.tray
  12494. ];
  12495. me.callParent();
  12496. },
  12497. afterLayout: function() {
  12498. var me = this;
  12499. me.callParent();
  12500. me.windowBar.el.on('contextmenu', me.onButtonContextMenu, me);
  12501. },
  12502. /**
  12503. * This method returns the configuration object for the Quick Start toolbar. A derived
  12504. * class can override this method, call the base version to build the config and
  12505. * then modify the returned object before returning it.
  12506. */
  12507. getQuickStart: function() {
  12508. var me = this,
  12509. ret = {
  12510. minWidth: 20,
  12511. width: Ext.themeName === 'neptune' ? 70 : 60,
  12512. items: [],
  12513. enableOverflow: true
  12514. };
  12515. Ext.each(this.quickStart, function(item) {
  12516. ret.items.push({
  12517. tooltip: {
  12518. text: item.name,
  12519. align: 'bl-tl'
  12520. },
  12521. // tooltip: item.name,
  12522. overflowText: item.name,
  12523. iconCls: item.iconCls,
  12524. module: item.module,
  12525. handler: me.onQuickStartClick,
  12526. scope: me
  12527. });
  12528. });
  12529. return ret;
  12530. },
  12531. /**
  12532. * This method returns the configuration object for the Tray toolbar. A derived
  12533. * class can override this method, call the base version to build the config and
  12534. * then modify the returned object before returning it.
  12535. */
  12536. getTrayConfig: function() {
  12537. var ret = {
  12538. items: this.trayItems
  12539. };
  12540. delete this.trayItems;
  12541. return ret;
  12542. },
  12543. getWindowBarConfig: function() {
  12544. return {
  12545. flex: 1,
  12546. cls: 'ux-desktop-windowbar',
  12547. items: [
  12548. '&#160;'
  12549. ],
  12550. layout: {
  12551. overflowHandler: 'Scroller'
  12552. }
  12553. };
  12554. },
  12555. getWindowBtnFromEl: function(el) {
  12556. var c = this.windowBar.getChildByElement(el);
  12557. return c || null;
  12558. },
  12559. onQuickStartClick: function(btn) {
  12560. var module = this.app.getModule(btn.module),
  12561. window;
  12562. if (module) {
  12563. window = module.createWindow();
  12564. window.show();
  12565. }
  12566. },
  12567. onButtonContextMenu: function(e) {
  12568. var me = this,
  12569. t = e.getTarget(),
  12570. btn = me.getWindowBtnFromEl(t);
  12571. if (btn) {
  12572. e.stopEvent();
  12573. me.windowMenu.theWin = btn.win;
  12574. me.windowMenu.showBy(t);
  12575. }
  12576. },
  12577. onWindowBtnClick: function(btn) {
  12578. var win = btn.win;
  12579. if (win.minimized || win.hidden) {
  12580. btn.disable();
  12581. win.show(null, function() {
  12582. btn.enable();
  12583. });
  12584. } else if (win.active) {
  12585. btn.disable();
  12586. win.on('hide', function() {
  12587. btn.enable();
  12588. }, null, {
  12589. single: true
  12590. });
  12591. win.minimize();
  12592. } else {
  12593. win.toFront();
  12594. }
  12595. },
  12596. addTaskButton: function(win) {
  12597. var config = {
  12598. iconCls: win.iconCls,
  12599. enableToggle: true,
  12600. toggleGroup: 'all',
  12601. width: 140,
  12602. margin: '0 2 0 3',
  12603. text: Ext.util.Format.ellipsis(win.title, 20),
  12604. listeners: {
  12605. click: this.onWindowBtnClick,
  12606. scope: this
  12607. },
  12608. win: win
  12609. },
  12610. cmp = this.windowBar.add(config);
  12611. cmp.toggle(true);
  12612. return cmp;
  12613. },
  12614. removeTaskButton: function(btn) {
  12615. var found,
  12616. me = this;
  12617. me.windowBar.items.each(function(item) {
  12618. if (item === btn) {
  12619. found = item;
  12620. }
  12621. return !found;
  12622. });
  12623. if (found) {
  12624. me.windowBar.remove(found);
  12625. }
  12626. return found;
  12627. },
  12628. setActiveButton: function(btn) {
  12629. if (btn) {
  12630. btn.toggle(true);
  12631. } else {
  12632. this.windowBar.items.each(function(item) {
  12633. if (item.isButton) {
  12634. item.toggle(false);
  12635. }
  12636. });
  12637. }
  12638. }
  12639. });
  12640. /**
  12641. * @class Ext.ux.desktop.TrayClock
  12642. * @extends Ext.toolbar.TextItem
  12643. * This class displays a clock on the toolbar.
  12644. */
  12645. Ext.define('Ext.ux.desktop.TrayClock', {
  12646. extend: 'Ext.toolbar.TextItem',
  12647. alias: 'widget.trayclock',
  12648. cls: 'ux-desktop-trayclock',
  12649. html: '&#160;',
  12650. timeFormat: 'g:i A',
  12651. tpl: '{time}',
  12652. initComponent: function() {
  12653. var me = this;
  12654. me.callParent();
  12655. if (typeof (me.tpl) === 'string') {
  12656. me.tpl = new Ext.XTemplate(me.tpl);
  12657. }
  12658. },
  12659. afterRender: function() {
  12660. var me = this;
  12661. Ext.defer(me.updateTime, 100, me);
  12662. me.callParent();
  12663. },
  12664. doDestroy: function() {
  12665. var me = this;
  12666. if (me.timer) {
  12667. window.clearTimeout(me.timer);
  12668. me.timer = null;
  12669. }
  12670. me.callParent();
  12671. },
  12672. updateTime: function() {
  12673. var me = this,
  12674. time = Ext.Date.format(new Date(), me.timeFormat),
  12675. text = me.tpl.apply({
  12676. time: time
  12677. });
  12678. if (me.lastText !== text) {
  12679. me.setText(text);
  12680. me.lastText = text;
  12681. }
  12682. me.timer = Ext.defer(me.updateTime, 10000, me);
  12683. }
  12684. });
  12685. /*
  12686. * Ext JS Library
  12687. * Copyright(c) 2006-2015 Sencha Inc.
  12688. * licensing@sencha.com
  12689. * http://www.sencha.com/license
  12690. */
  12691. /**
  12692. * From code originally written by David Davis
  12693. *
  12694. * For HTML5 video to work, your server must
  12695. * send the right content type, for more info see:
  12696. * <http://developer.mozilla.org/En/HTML/Element/Video>
  12697. * @class Ext.ux.desktop.Video
  12698. */
  12699. Ext.define('Ext.ux.desktop.Video', {
  12700. extend: 'Ext.panel.Panel',
  12701. alias: 'widget.video',
  12702. layout: 'fit',
  12703. autoplay: false,
  12704. controls: true,
  12705. bodyStyle: 'background-color:#000;color:#fff',
  12706. html: '',
  12707. /* eslint-disable max-len, indent */
  12708. tpl: [
  12709. '<video id="{id}-video" autoPlay="{autoplay}" controls="{controls}" poster="{poster}" start="{start}" loopstart="{loopstart}" loopend="{loopend}" autobuffer="{autobuffer}" loop="{loop}" style="width:100%;height:100%">',
  12710. '<tpl for="src">',
  12711. '<source src="{src}" type="{type}"/>',
  12712. '</tpl>',
  12713. '{html}',
  12714. '</video>'
  12715. ],
  12716. /* eslint-enable max-len, indent */
  12717. initComponent: function() {
  12718. var me = this,
  12719. fallback, cfg, chrome;
  12720. if (me.fallbackHTML) {
  12721. fallback = me.fallbackHTML;
  12722. } else {
  12723. fallback = "Your browser does not support HTML5 Video. ";
  12724. if (Ext.isChrome) {
  12725. fallback += 'Upgrade Chrome.';
  12726. } else if (Ext.isGecko) {
  12727. fallback += 'Upgrade to Firefox 3.5 or newer.';
  12728. } else {
  12729. chrome = '<a href="http://www.google.com/chrome">Chrome</a>';
  12730. fallback += 'Please try <a href="http://www.mozilla.com">Firefox</a>';
  12731. if (Ext.isIE) {
  12732. fallback += ', ' + chrome + ' or <a href="http://www.apple.com/safari/">Safari</a>.';
  12733. } else {
  12734. fallback += ' or ' + chrome + '.';
  12735. }
  12736. }
  12737. }
  12738. me.fallbackHTML = fallback;
  12739. cfg = me.data = Ext.copyTo({
  12740. tag: 'video',
  12741. html: fallback
  12742. }, me, 'id,poster,start,loopstart,loopend,playcount,autobuffer,loop');
  12743. // just having the params exist enables them
  12744. if (me.autoplay) {
  12745. cfg.autoplay = 1;
  12746. }
  12747. if (me.controls) {
  12748. cfg.controls = 1;
  12749. }
  12750. // handle multiple sources
  12751. if (Ext.isArray(me.src)) {
  12752. cfg.src = me.src;
  12753. } else {
  12754. cfg.src = [
  12755. {
  12756. src: me.src
  12757. }
  12758. ];
  12759. }
  12760. me.callParent();
  12761. },
  12762. afterRender: function() {
  12763. var me = this,
  12764. el;
  12765. me.callParent();
  12766. me.video = me.body.getById(me.id + '-video');
  12767. el = me.video.dom;
  12768. me.supported = (el && el.tagName.toLowerCase() === 'video');
  12769. if (me.supported) {
  12770. me.video.on('error', me.onVideoError, me);
  12771. }
  12772. },
  12773. getFallback: function() {
  12774. return '<h1 style="background-color:#ff4f4f;padding: 10px;">' + this.fallbackHTML + '</h1>';
  12775. },
  12776. onVideoError: function() {
  12777. var me = this;
  12778. me.video.remove();
  12779. me.supported = false;
  12780. me.body.createChild(me.getFallback());
  12781. },
  12782. doDestroy: function() {
  12783. var me = this,
  12784. video = me.video,
  12785. videoDom;
  12786. video = me.video;
  12787. if (me.supported && video) {
  12788. videoDom = video.dom;
  12789. if (videoDom && videoDom.pause) {
  12790. videoDom.pause();
  12791. }
  12792. video.remove();
  12793. me.video = null;
  12794. }
  12795. me.callParent();
  12796. }
  12797. });
  12798. /*
  12799. * Ext JS Library
  12800. * Copyright(c) 2006-2014 Sencha Inc.
  12801. * licensing@sencha.com
  12802. * http://www.sencha.com/license
  12803. */
  12804. /**
  12805. * @class Ext.ux.desktop.Wallpaper
  12806. * @extends Ext.Component
  12807. * <p>This component renders an image that stretches to fill the component.</p>
  12808. */
  12809. Ext.define('Ext.ux.desktop.Wallpaper', {
  12810. extend: 'Ext.Component',
  12811. alias: 'widget.wallpaper',
  12812. cls: 'ux-wallpaper',
  12813. html: '<img src="' + Ext.BLANK_IMAGE_URL + '">',
  12814. stretch: false,
  12815. wallpaper: null,
  12816. stateful: true,
  12817. stateId: 'desk-wallpaper',
  12818. afterRender: function() {
  12819. var me = this;
  12820. me.callParent();
  12821. me.setWallpaper(me.wallpaper, me.stretch);
  12822. },
  12823. applyState: function() {
  12824. var me = this,
  12825. old = me.wallpaper;
  12826. me.callParent(arguments);
  12827. if (old !== me.wallpaper) {
  12828. me.setWallpaper(me.wallpaper);
  12829. }
  12830. },
  12831. getState: function() {
  12832. return this.wallpaper && {
  12833. wallpaper: this.wallpaper
  12834. };
  12835. },
  12836. setWallpaper: function(wallpaper, stretch) {
  12837. var me = this,
  12838. imgEl, bkgnd;
  12839. me.stretch = (stretch !== false);
  12840. me.wallpaper = wallpaper;
  12841. if (me.rendered) {
  12842. imgEl = me.el.dom.firstChild;
  12843. if (!wallpaper || wallpaper === Ext.BLANK_IMAGE_URL) {
  12844. Ext.fly(imgEl).hide();
  12845. } else if (me.stretch) {
  12846. imgEl.src = wallpaper;
  12847. me.el.removeCls('ux-wallpaper-tiled');
  12848. Ext.fly(imgEl).setStyle({
  12849. width: '100%',
  12850. height: '100%'
  12851. }).show();
  12852. } else {
  12853. Ext.fly(imgEl).hide();
  12854. bkgnd = 'url(' + wallpaper + ')';
  12855. me.el.addCls('ux-wallpaper-tiled');
  12856. }
  12857. me.el.setStyle({
  12858. backgroundImage: bkgnd || ''
  12859. });
  12860. if (me.stateful) {
  12861. me.saveState();
  12862. }
  12863. }
  12864. return me;
  12865. }
  12866. });
  12867. /**
  12868. * Recorder manager.
  12869. * Used as a bookmarklet:
  12870. *
  12871. * javascript:void(window.open("../ux/event/RecorderManager.html","recmgr"))
  12872. */
  12873. Ext.define('Ext.ux.event.RecorderManager', {
  12874. extend: 'Ext.panel.Panel',
  12875. alias: 'widget.eventrecordermanager',
  12876. uses: [
  12877. 'Ext.ux.event.Recorder',
  12878. 'Ext.ux.event.Player'
  12879. ],
  12880. layout: 'fit',
  12881. buttonAlign: 'left',
  12882. eventsToIgnore: {
  12883. mousemove: 1,
  12884. mouseover: 1,
  12885. mouseout: 1
  12886. },
  12887. bodyBorder: false,
  12888. playSpeed: 1,
  12889. initComponent: function() {
  12890. var me = this,
  12891. events;
  12892. me.recorder = new Ext.ux.event.Recorder({
  12893. attachTo: me.attachTo,
  12894. listeners: {
  12895. add: me.updateEvents,
  12896. coalesce: me.updateEvents,
  12897. buffer: 200,
  12898. scope: me
  12899. }
  12900. });
  12901. me.recorder.eventsToRecord = Ext.apply({}, me.recorder.eventsToRecord);
  12902. function speed(text, value) {
  12903. return {
  12904. text: text,
  12905. speed: value,
  12906. group: 'speed',
  12907. checked: value === me.playSpeed,
  12908. handler: me.onPlaySpeed,
  12909. scope: me
  12910. };
  12911. }
  12912. me.tbar = [
  12913. {
  12914. text: 'Record',
  12915. xtype: 'splitbutton',
  12916. whenIdle: true,
  12917. handler: me.onRecord,
  12918. scope: me,
  12919. menu: me.makeRecordButtonMenu()
  12920. },
  12921. {
  12922. text: 'Play',
  12923. xtype: 'splitbutton',
  12924. whenIdle: true,
  12925. handler: me.onPlay,
  12926. scope: me,
  12927. menu: [
  12928. speed('Qarter Speed (0.25x)', 0.25),
  12929. speed('Half Speed (0.5x)', 0.5),
  12930. speed('3/4 Speed (0.75x)', 0.75),
  12931. '-',
  12932. speed('Recorded Speed (1x)', 1),
  12933. speed('Double Speed (2x)', 2),
  12934. speed('Quad Speed (4x)', 4),
  12935. '-',
  12936. speed('Full Speed', 1000)
  12937. ]
  12938. },
  12939. {
  12940. text: 'Clear',
  12941. whenIdle: true,
  12942. handler: me.onClear,
  12943. scope: me
  12944. },
  12945. '->',
  12946. {
  12947. text: 'Stop',
  12948. whenActive: true,
  12949. disabled: true,
  12950. handler: me.onStop,
  12951. scope: me
  12952. }
  12953. ];
  12954. events = me.attachTo && me.attachTo.testEvents;
  12955. me.items = [
  12956. {
  12957. xtype: 'textarea',
  12958. itemId: 'eventView',
  12959. fieldStyle: 'font-family: monospace',
  12960. selectOnFocus: true,
  12961. emptyText: 'Events go here!',
  12962. value: events ? me.stringifyEvents(events) : '',
  12963. scrollToBottom: function() {
  12964. var inputEl = this.inputEl.dom;
  12965. inputEl.scrollTop = inputEl.scrollHeight;
  12966. }
  12967. }
  12968. ];
  12969. me.fbar = [
  12970. {
  12971. xtype: 'tbtext',
  12972. text: 'Attached To: ' + (me.attachTo && me.attachTo.location.href)
  12973. }
  12974. ];
  12975. me.callParent();
  12976. },
  12977. makeRecordButtonMenu: function() {
  12978. var ret = [],
  12979. subs = {},
  12980. eventsToRec = this.recorder.eventsToRecord,
  12981. ignoredEvents = this.eventsToIgnore;
  12982. Ext.Object.each(eventsToRec, function(name, value) {
  12983. var sub = subs[value.kind];
  12984. if (!sub) {
  12985. subs[value.kind] = sub = [];
  12986. ret.push({
  12987. text: value.kind,
  12988. menu: sub
  12989. });
  12990. }
  12991. sub.push({
  12992. text: name,
  12993. checked: true,
  12994. handler: function(menuItem) {
  12995. if (menuItem.checked) {
  12996. eventsToRec[name] = value;
  12997. } else {
  12998. delete eventsToRec[name];
  12999. }
  13000. }
  13001. });
  13002. if (ignoredEvents[name]) {
  13003. sub[sub.length - 1].checked = false;
  13004. Ext.defer(function() {
  13005. delete eventsToRec[name];
  13006. }, 1);
  13007. }
  13008. });
  13009. function less(lhs, rhs) {
  13010. return (lhs.text < rhs.text) ? -1 : ((rhs.text < lhs.text) ? 1 : 0);
  13011. }
  13012. ret.sort(less);
  13013. Ext.Array.each(ret, function(sub) {
  13014. sub.menu.sort(less);
  13015. });
  13016. return ret;
  13017. },
  13018. getEventView: function() {
  13019. return this.down('#eventView');
  13020. },
  13021. onClear: function() {
  13022. var view = this.getEventView();
  13023. view.setValue('');
  13024. },
  13025. onPlay: function() {
  13026. var me = this,
  13027. view = me.getEventView(),
  13028. events = view.getValue();
  13029. if (events) {
  13030. events = Ext.decode(events);
  13031. if (events.length) {
  13032. me.player = Ext.create('Ext.ux.event.Player', {
  13033. attachTo: window.opener,
  13034. eventQueue: events,
  13035. speed: me.playSpeed,
  13036. listeners: {
  13037. stop: me.onPlayStop,
  13038. scope: me
  13039. }
  13040. });
  13041. me.player.start();
  13042. me.syncBtnUI();
  13043. }
  13044. }
  13045. },
  13046. onPlayStop: function() {
  13047. this.player = null;
  13048. this.syncBtnUI();
  13049. },
  13050. onPlaySpeed: function(menuitem) {
  13051. this.playSpeed = menuitem.speed;
  13052. },
  13053. onRecord: function() {
  13054. this.recorder.start();
  13055. this.syncBtnUI();
  13056. },
  13057. onStop: function() {
  13058. var me = this;
  13059. if (me.player) {
  13060. me.player.stop();
  13061. me.player = null;
  13062. } else {
  13063. me.recorder.stop();
  13064. }
  13065. me.syncBtnUI();
  13066. me.updateEvents();
  13067. },
  13068. syncBtnUI: function() {
  13069. var me = this,
  13070. idle = !me.player && !me.recorder.active,
  13071. view;
  13072. Ext.each(me.query('[whenIdle]'), function(btn) {
  13073. btn.setDisabled(!idle);
  13074. });
  13075. Ext.each(me.query('[whenActive]'), function(btn) {
  13076. btn.setDisabled(idle);
  13077. });
  13078. view = me.getEventView();
  13079. view.setReadOnly(!idle);
  13080. },
  13081. stringifyEvents: function(events) {
  13082. var line,
  13083. lines = [];
  13084. Ext.each(events, function(ev) {
  13085. line = [];
  13086. Ext.Object.each(ev, function(name, value) {
  13087. if (line.length) {
  13088. line.push(', ');
  13089. } else {
  13090. line.push(' { ');
  13091. }
  13092. line.push(name, ': ');
  13093. line.push(Ext.encode(value));
  13094. });
  13095. line.push(' }');
  13096. lines.push(line.join(''));
  13097. });
  13098. return '[\n' + lines.join(',\n') + '\n]';
  13099. },
  13100. updateEvents: function() {
  13101. var me = this,
  13102. text = me.stringifyEvents(me.recorder.getRecordedEvents()),
  13103. view = me.getEventView();
  13104. view.setValue(text);
  13105. view.scrollToBottom();
  13106. }
  13107. });
  13108. /**
  13109. * A control that allows selection of multiple items in a list.
  13110. */
  13111. Ext.define('Ext.ux.form.MultiSelect', {
  13112. extend: 'Ext.form.FieldContainer',
  13113. mixins: [
  13114. 'Ext.util.StoreHolder',
  13115. 'Ext.form.field.Field'
  13116. ],
  13117. alternateClassName: 'Ext.ux.Multiselect',
  13118. alias: [
  13119. 'widget.multiselectfield',
  13120. 'widget.multiselect'
  13121. ],
  13122. requires: [
  13123. 'Ext.panel.Panel',
  13124. 'Ext.view.BoundList',
  13125. 'Ext.layout.container.Fit'
  13126. ],
  13127. uses: [
  13128. 'Ext.view.DragZone',
  13129. 'Ext.view.DropZone'
  13130. ],
  13131. layout: 'anchor',
  13132. /**
  13133. * @cfg {String} [dragGroup=""] The ddgroup name for the MultiSelect DragZone.
  13134. */
  13135. /**
  13136. * @cfg {String} [dropGroup=""] The ddgroup name for the MultiSelect DropZone.
  13137. */
  13138. /**
  13139. * @cfg {String} [title=""] A title for the underlying panel.
  13140. */
  13141. /**
  13142. * @cfg {Boolean} [ddReorder=false] Whether the items in the MultiSelect list are drag/drop
  13143. * reorderable.
  13144. */
  13145. ddReorder: false,
  13146. /**
  13147. * @cfg {Object/Array} tbar An optional toolbar to be inserted at the top of the control's
  13148. * selection list. This can be a {@link Ext.toolbar.Toolbar} object, a toolbar config,
  13149. * or an array of buttons/button configs to be added to the toolbar.
  13150. * See {@link Ext.panel.Panel#tbar}.
  13151. */
  13152. /**
  13153. * @cfg {String} [appendOnly=false] `true` if the list should only allow append drops
  13154. * when drag/drop is enabled. This is useful for lists which are sorted.
  13155. */
  13156. appendOnly: false,
  13157. /**
  13158. * @cfg {String} [displayField="text"] Name of the desired display field in the dataset.
  13159. */
  13160. displayField: 'text',
  13161. /**
  13162. * @cfg {String} [valueField="text"] Name of the desired value field in the dataset.
  13163. */
  13164. /**
  13165. * @cfg {Boolean} [allowBlank=true] `false` to require at least one item in the list
  13166. * to be selected, `true` to allow no selection.
  13167. */
  13168. allowBlank: true,
  13169. /**
  13170. * @cfg {Number} [minSelections=0] Minimum number of selections allowed.
  13171. */
  13172. minSelections: 0,
  13173. /**
  13174. * @cfg {Number} [maxSelections=Number.MAX_VALUE] Maximum number of selections allowed.
  13175. */
  13176. maxSelections: Number.MAX_VALUE,
  13177. /**
  13178. * @cfg {String} [blankText="This field is required"] Default text displayed when the control
  13179. * contains no items.
  13180. */
  13181. blankText: 'This field is required',
  13182. /**
  13183. * @cfg {String} [minSelectionsText="Minimum {0}item(s) required"]
  13184. * Validation message displayed when {@link #minSelections} is not met.
  13185. * The {0} token will be replaced by the value of {@link #minSelections}.
  13186. */
  13187. minSelectionsText: 'Minimum {0} item(s) required',
  13188. /**
  13189. * @cfg {String} [maxSelectionsText="Maximum {0}item(s) allowed"]
  13190. * Validation message displayed when {@link #maxSelections} is not met
  13191. * The {0} token will be replaced by the value of {@link #maxSelections}.
  13192. */
  13193. maxSelectionsText: 'Maximum {0} item(s) required',
  13194. /**
  13195. * @cfg {String} [delimiter=","] The string used to delimit the selected values when
  13196. * {@link #getSubmitValue submitting} the field as part of a form. If you wish to have
  13197. * the selected values submitted as separate parameters rather than a single
  13198. * delimited parameter, set this to `null`.
  13199. */
  13200. delimiter: ',',
  13201. /**
  13202. * @cfg {String} [dragText="{0} Item{1}"] The text to show while dragging items.
  13203. * {0} will be replaced by the number of items. {1} will be replaced by the plural
  13204. * form if there is more than 1 item.
  13205. */
  13206. dragText: '{0} Item{1}',
  13207. /**
  13208. * @cfg {Ext.data.Store/Array} store The data source to which this MultiSelect is bound
  13209. * (defaults to `undefined`).
  13210. * Acceptable values for this property are:
  13211. * <div class="mdetail-params"><ul>
  13212. * <li><b>any {@link Ext.data.Store Store} subclass</b></li>
  13213. * <li><b>an Array</b> : Arrays will be converted to a {@link Ext.data.ArrayStore} internally.
  13214. * <div class="mdetail-params"><ul>
  13215. * <li><b>1-dimensional array</b> : (e.g., <tt>['Foo','Bar']</tt>)<div class="sub-desc">
  13216. * A 1-dimensional array will automatically be expanded (each array item will be the combo
  13217. * {@link #valueField value} and {@link #displayField text})</div></li>
  13218. * <li><b>2-dimensional array</b> : (e.g., `[['f','Foo'],['b','Bar']]`)<div class="sub-desc">
  13219. * For a multi-dimensional array, the value in index 0 of each item will be assumed to be
  13220. * the combo {@link #valueField value}, while the value at index 1 is assumed to be the combo
  13221. * {@link #displayField text}.
  13222. * </div></li></ul></div></li></ul></div>
  13223. */
  13224. ignoreSelectChange: 0,
  13225. /**
  13226. * @cfg {Object} listConfig
  13227. * An optional set of configuration properties that will be passed to the
  13228. * {@link Ext.view.BoundList}'s constructor. Any configuration that is valid for BoundList
  13229. * can be included.
  13230. */
  13231. /**
  13232. * @cfg {Number} [pageSize=10] The number of items to advance on pageUp and pageDown
  13233. */
  13234. pageSize: 10,
  13235. initComponent: function() {
  13236. var me = this;
  13237. me.items = me.setupItems();
  13238. me.bindStore(me.store, true);
  13239. me.callParent();
  13240. me.initField();
  13241. },
  13242. setupItems: function() {
  13243. var me = this;
  13244. me.boundList = new Ext.view.BoundList(Ext.apply({
  13245. anchor: 'none 100%',
  13246. border: 1,
  13247. multiSelect: true,
  13248. store: me.store,
  13249. displayField: me.displayField,
  13250. disabled: me.disabled,
  13251. tabIndex: 0,
  13252. navigationModel: {
  13253. type: 'default'
  13254. }
  13255. }, me.listConfig));
  13256. me.boundList.getNavigationModel().addKeyBindings({
  13257. pageUp: me.onKeyPageUp,
  13258. pageDown: me.onKeyPageDown,
  13259. scope: me
  13260. });
  13261. me.boundList.getSelectionModel().on('selectionchange', me.onSelectChange, me);
  13262. // Boundlist expects a reference to its pickerField for when an item is selected
  13263. // (see Boundlist#onItemClick).
  13264. me.boundList.pickerField = me;
  13265. // Only need to wrap the BoundList in a Panel if we have a title.
  13266. if (!me.title) {
  13267. return me.boundList;
  13268. }
  13269. // Wrap to add a title
  13270. me.boundList.border = false;
  13271. return {
  13272. xtype: 'panel',
  13273. isAriaRegion: false,
  13274. border: true,
  13275. anchor: 'none 100%',
  13276. layout: 'anchor',
  13277. title: me.title,
  13278. tbar: me.tbar,
  13279. items: me.boundList
  13280. };
  13281. },
  13282. onSelectChange: function(selModel, selections) {
  13283. if (!this.ignoreSelectChange) {
  13284. this.setValue(selections);
  13285. }
  13286. },
  13287. getSelected: function() {
  13288. return this.boundList.getSelectionModel().getSelection();
  13289. },
  13290. // compare array values
  13291. isEqual: function(v1, v2) {
  13292. var fromArray = Ext.Array.from,
  13293. i = 0,
  13294. len;
  13295. v1 = fromArray(v1);
  13296. v2 = fromArray(v2);
  13297. len = v1.length;
  13298. if (len !== v2.length) {
  13299. return false;
  13300. }
  13301. for (; i < len; i++) {
  13302. if (v2[i] !== v1[i]) {
  13303. return false;
  13304. }
  13305. }
  13306. return true;
  13307. },
  13308. afterRender: function() {
  13309. var me = this,
  13310. boundList, scrollable, records, panel;
  13311. me.callParent();
  13312. boundList = me.boundList;
  13313. scrollable = boundList && boundList.getScrollable();
  13314. if (me.selectOnRender) {
  13315. records = me.getRecordsForValue(me.value);
  13316. if (records.length) {
  13317. ++me.ignoreSelectChange;
  13318. boundList.getSelectionModel().select(records);
  13319. --me.ignoreSelectChange;
  13320. }
  13321. delete me.toSelect;
  13322. }
  13323. if (me.ddReorder && !me.dragGroup && !me.dropGroup) {
  13324. me.dragGroup = me.dropGroup = 'MultiselectDD-' + Ext.id();
  13325. }
  13326. if (me.draggable || me.dragGroup) {
  13327. me.dragZone = Ext.create('Ext.view.DragZone', {
  13328. view: boundList,
  13329. ddGroup: me.dragGroup,
  13330. dragText: me.dragText,
  13331. containerScroll: !!scrollable,
  13332. scrollEl: scrollable && scrollable.getElement()
  13333. });
  13334. }
  13335. if (me.droppable || me.dropGroup) {
  13336. me.dropZone = Ext.create('Ext.view.DropZone', {
  13337. view: boundList,
  13338. ddGroup: me.dropGroup,
  13339. handleNodeDrop: function(data, dropRecord, position) {
  13340. var view = this.view,
  13341. store = view.getStore(),
  13342. records = data.records,
  13343. index;
  13344. // remove the Models from the source Store
  13345. data.view.store.remove(records);
  13346. index = store.indexOf(dropRecord);
  13347. if (position === 'after') {
  13348. index++;
  13349. }
  13350. store.insert(index, records);
  13351. view.getSelectionModel().select(records);
  13352. me.fireEvent('drop', me, records);
  13353. }
  13354. });
  13355. }
  13356. panel = me.down('panel');
  13357. if (panel && boundList) {
  13358. boundList.ariaEl.dom.setAttribute('aria-labelledby', panel.header.id + '-title-textEl');
  13359. }
  13360. },
  13361. onKeyPageUp: function(e) {
  13362. var me = this,
  13363. pageSize = me.pageSize,
  13364. boundList = me.boundList,
  13365. nm = boundList.getNavigationModel(),
  13366. oldIdx, newIdx;
  13367. oldIdx = nm.recordIndex;
  13368. // Unlike up arrow, pgUp does not wrap but goes to the first item
  13369. newIdx = oldIdx > pageSize ? oldIdx - pageSize : 0;
  13370. nm.setPosition(newIdx, e);
  13371. },
  13372. onKeyPageDown: function(e) {
  13373. var me = this,
  13374. pageSize = me.pageSize,
  13375. boundList = me.boundList,
  13376. nm = boundList.getNavigationModel(),
  13377. count, oldIdx, newIdx;
  13378. count = boundList.getStore().getCount();
  13379. oldIdx = nm.recordIndex;
  13380. // Unlike down arrow, pgDown does not wrap but goes to the last item
  13381. newIdx = oldIdx < (count - pageSize) ? oldIdx + pageSize : count - 1;
  13382. nm.setPosition(newIdx, e);
  13383. },
  13384. isValid: function() {
  13385. var me = this,
  13386. disabled = me.disabled,
  13387. validate = me.forceValidation || !disabled;
  13388. return validate ? me.validateValue(me.value) : disabled;
  13389. },
  13390. validateValue: function(value) {
  13391. var me = this,
  13392. errors = me.getErrors(value),
  13393. isValid = Ext.isEmpty(errors);
  13394. if (!me.preventMark) {
  13395. if (isValid) {
  13396. me.clearInvalid();
  13397. } else {
  13398. me.markInvalid(errors);
  13399. }
  13400. }
  13401. return isValid;
  13402. },
  13403. markInvalid: function(errors) {
  13404. // Save the message and fire the 'invalid' event
  13405. var me = this,
  13406. oldMsg = me.getActiveError();
  13407. me.setActiveErrors(Ext.Array.from(errors));
  13408. if (oldMsg !== me.getActiveError()) {
  13409. me.updateLayout();
  13410. }
  13411. },
  13412. /**
  13413. * Clear any invalid styles/messages for this field.
  13414. *
  13415. * __Note:__ this method does not cause the Field's {@link #validate} or {@link #isValid}
  13416. * methods to return `true` if the value does not _pass_ validation. So simply clearing
  13417. * a field's errors will not necessarily allow submission of forms submitted with the
  13418. * {@link Ext.form.action.Submit#clientValidation} option set.
  13419. */
  13420. clearInvalid: function() {
  13421. // Clear the message and fire the 'valid' event
  13422. var me = this,
  13423. hadError = me.hasActiveError();
  13424. me.unsetActiveError();
  13425. if (hadError) {
  13426. me.updateLayout();
  13427. }
  13428. },
  13429. getSubmitData: function() {
  13430. var me = this,
  13431. data = null,
  13432. val;
  13433. if (!me.disabled && me.submitValue && !me.isFileUpload()) {
  13434. val = me.getSubmitValue();
  13435. if (val !== null) {
  13436. data = {};
  13437. data[me.getName()] = val;
  13438. }
  13439. }
  13440. return data;
  13441. },
  13442. /**
  13443. * Returns the value that would be included in a standard form submit for this field.
  13444. *
  13445. * @return {String} The value to be submitted, or `null`.
  13446. */
  13447. getSubmitValue: function() {
  13448. var me = this,
  13449. delimiter = me.delimiter,
  13450. val = me.getValue();
  13451. return Ext.isString(delimiter) ? val.join(delimiter) : val;
  13452. },
  13453. getValue: function() {
  13454. return this.value || [];
  13455. },
  13456. getRecordsForValue: function(value) {
  13457. var me = this,
  13458. records = [],
  13459. all = me.store.getRange(),
  13460. valueField = me.valueField,
  13461. i = 0,
  13462. allLen = all.length,
  13463. rec, j, valueLen;
  13464. for (valueLen = value.length; i < valueLen; ++i) {
  13465. for (j = 0; j < allLen; ++j) {
  13466. rec = all[j];
  13467. if (rec.get(valueField) === value[i]) {
  13468. records.push(rec);
  13469. }
  13470. }
  13471. }
  13472. return records;
  13473. },
  13474. setupValue: function(value) {
  13475. var delimiter = this.delimiter,
  13476. valueField = this.valueField,
  13477. i = 0,
  13478. out, len, item;
  13479. if (Ext.isDefined(value)) {
  13480. if (delimiter && Ext.isString(value)) {
  13481. value = value.split(delimiter);
  13482. } else if (!Ext.isArray(value)) {
  13483. value = [
  13484. value
  13485. ];
  13486. }
  13487. for (len = value.length; i < len; ++i) {
  13488. item = value[i];
  13489. if (item && item.isModel) {
  13490. value[i] = item.get(valueField);
  13491. }
  13492. }
  13493. out = Ext.Array.unique(value);
  13494. } else {
  13495. out = [];
  13496. }
  13497. return out;
  13498. },
  13499. setValue: function(value) {
  13500. var me = this,
  13501. selModel = me.boundList.getSelectionModel(),
  13502. store = me.store;
  13503. // Store not loaded yet - we cannot set the value
  13504. if (!store.getCount()) {
  13505. store.on({
  13506. load: Ext.Function.bind(me.setValue, me, [
  13507. value
  13508. ]),
  13509. single: true
  13510. });
  13511. return;
  13512. }
  13513. value = me.setupValue(value);
  13514. me.mixins.field.setValue.call(me, value);
  13515. if (me.rendered) {
  13516. ++me.ignoreSelectChange;
  13517. selModel.deselectAll();
  13518. if (value.length) {
  13519. selModel.select(me.getRecordsForValue(value));
  13520. }
  13521. --me.ignoreSelectChange;
  13522. } else {
  13523. me.selectOnRender = true;
  13524. }
  13525. },
  13526. clearValue: function() {
  13527. this.setValue([]);
  13528. },
  13529. onEnable: function() {
  13530. var list = this.boundList;
  13531. this.callParent();
  13532. if (list) {
  13533. list.enable();
  13534. }
  13535. },
  13536. onDisable: function() {
  13537. var list = this.boundList;
  13538. this.callParent();
  13539. if (list) {
  13540. list.disable();
  13541. }
  13542. },
  13543. getErrors: function(value) {
  13544. var me = this,
  13545. format = Ext.String.format,
  13546. errors = [],
  13547. numSelected;
  13548. value = Ext.Array.from(value || me.getValue());
  13549. numSelected = value.length;
  13550. if (!me.allowBlank && numSelected < 1) {
  13551. errors.push(me.blankText);
  13552. }
  13553. if (numSelected < me.minSelections) {
  13554. errors.push(format(me.minSelectionsText, me.minSelections));
  13555. }
  13556. if (numSelected > me.maxSelections) {
  13557. errors.push(format(me.maxSelectionsText, me.maxSelections));
  13558. }
  13559. return errors;
  13560. },
  13561. doDestroy: function() {
  13562. var me = this;
  13563. me.bindStore(null);
  13564. Ext.destroy(me.dragZone, me.dropZone, me.keyNav);
  13565. me.callParent();
  13566. },
  13567. onBindStore: function(store) {
  13568. var me = this,
  13569. boundList = this.boundList;
  13570. if (store.autoCreated) {
  13571. me.resolveDisplayField();
  13572. }
  13573. if (!Ext.isDefined(me.valueField)) {
  13574. me.valueField = me.displayField;
  13575. }
  13576. if (boundList) {
  13577. boundList.bindStore(store);
  13578. }
  13579. },
  13580. /**
  13581. * Applies auto-created store fields to field and boundlist
  13582. * @private
  13583. */
  13584. resolveDisplayField: function() {
  13585. var me = this,
  13586. boundList = me.boundList,
  13587. store = me.getStore();
  13588. me.valueField = me.displayField = 'field1';
  13589. if (!store.expanded) {
  13590. me.displayField = 'field2';
  13591. }
  13592. if (boundList) {
  13593. boundList.setDisplayField(me.displayField);
  13594. }
  13595. }
  13596. });
  13597. /*
  13598. * Note that this control will most likely remain as an example, and not as a core Ext form
  13599. * control. However, the API will be changing in a future release and so should not yet be
  13600. * treated as a final, stable API at this time.
  13601. */
  13602. /**
  13603. * A control that allows selection of between two Ext.ux.form.MultiSelect controls.
  13604. */
  13605. Ext.define('Ext.ux.form.ItemSelector', {
  13606. extend: 'Ext.ux.form.MultiSelect',
  13607. alias: [
  13608. 'widget.itemselectorfield',
  13609. 'widget.itemselector'
  13610. ],
  13611. alternateClassName: [
  13612. 'Ext.ux.ItemSelector'
  13613. ],
  13614. requires: [
  13615. 'Ext.button.Button',
  13616. 'Ext.ux.form.MultiSelect'
  13617. ],
  13618. /**
  13619. * @cfg {Boolean} [hideNavIcons=false] True to hide the navigation icons
  13620. */
  13621. hideNavIcons: false,
  13622. /**
  13623. * @cfg {Array} buttons Defines the set of buttons that should be displayed in between
  13624. * the ItemSelector fields. Defaults to `['top', 'up', 'add', 'remove', 'down', 'bottom']`.
  13625. * These names are used to build the button CSS class names, and to look up the button text
  13626. * labels in {@link #buttonsText}. This can be overridden with a custom Array to change
  13627. * which buttons are displayed or their order.
  13628. */
  13629. buttons: [
  13630. 'top',
  13631. 'up',
  13632. 'add',
  13633. 'remove',
  13634. 'down',
  13635. 'bottom'
  13636. ],
  13637. /**
  13638. * @cfg {Object} buttonsText The tooltips for the {@link #buttons}.
  13639. * Labels for buttons.
  13640. */
  13641. buttonsText: {
  13642. top: "Move to Top",
  13643. up: "Move Up",
  13644. add: "Add to Selected",
  13645. remove: "Remove from Selected",
  13646. down: "Move Down",
  13647. bottom: "Move to Bottom"
  13648. },
  13649. layout: {
  13650. type: 'hbox',
  13651. align: 'stretch'
  13652. },
  13653. ariaRole: 'group',
  13654. initComponent: function() {
  13655. var me = this;
  13656. me.ddGroup = me.id + '-dd';
  13657. me.ariaRenderAttributes = me.ariaRenderAttributes || {};
  13658. me.ariaRenderAttributes['aria-labelledby'] = me.id + '-labelEl';
  13659. me.callParent();
  13660. // bindStore must be called after the fromField has been created because
  13661. // it copies records from our configured Store into the fromField's Store
  13662. me.bindStore(me.store);
  13663. },
  13664. createList: function(title) {
  13665. var me = this;
  13666. return Ext.create('Ext.ux.form.MultiSelect', {
  13667. // We don't want the multiselects themselves to act like fields,
  13668. // so override these methods to prevent them from including
  13669. // any of their values
  13670. submitValue: false,
  13671. getSubmitData: function() {
  13672. return null;
  13673. },
  13674. getModelData: function() {
  13675. return null;
  13676. },
  13677. flex: 1,
  13678. dragGroup: me.ddGroup,
  13679. dropGroup: me.ddGroup,
  13680. title: title,
  13681. store: {
  13682. model: me.store.model,
  13683. data: []
  13684. },
  13685. displayField: me.displayField,
  13686. valueField: me.valueField,
  13687. disabled: me.disabled,
  13688. listeners: {
  13689. boundList: {
  13690. scope: me,
  13691. itemdblclick: me.onItemDblClick,
  13692. drop: me.syncValue
  13693. }
  13694. }
  13695. });
  13696. },
  13697. setupItems: function() {
  13698. var me = this;
  13699. me.fromField = me.createList(me.fromTitle);
  13700. me.toField = me.createList(me.toTitle);
  13701. return [
  13702. me.fromField,
  13703. {
  13704. xtype: 'toolbar',
  13705. margin: '0 4',
  13706. padding: 0,
  13707. layout: {
  13708. type: 'vbox',
  13709. pack: 'center'
  13710. },
  13711. items: me.createButtons()
  13712. },
  13713. me.toField
  13714. ];
  13715. },
  13716. createButtons: function() {
  13717. var me = this,
  13718. buttons = [];
  13719. if (!me.hideNavIcons) {
  13720. Ext.Array.forEach(me.buttons, function(name) {
  13721. buttons.push({
  13722. xtype: 'button',
  13723. ui: 'default',
  13724. tooltip: me.buttonsText[name],
  13725. ariaLabel: me.buttonsText[name],
  13726. handler: me['on' + Ext.String.capitalize(name) + 'BtnClick'],
  13727. cls: Ext.baseCSSPrefix + 'form-itemselector-btn',
  13728. iconCls: Ext.baseCSSPrefix + 'form-itemselector-' + name,
  13729. navBtn: true,
  13730. scope: me,
  13731. margin: '4 0 0 0'
  13732. });
  13733. });
  13734. }
  13735. return buttons;
  13736. },
  13737. /**
  13738. * Get the selected records from the specified list.
  13739. *
  13740. * Records will be returned *in store order*, not in order of selection.
  13741. * @param {Ext.view.BoundList} list The list to read selections from.
  13742. * @return {Ext.data.Model[]} The selected records in store order.
  13743. *
  13744. */
  13745. getSelections: function(list) {
  13746. var store = list.getStore();
  13747. return Ext.Array.sort(list.getSelectionModel().getSelection(), function(a, b) {
  13748. a = store.indexOf(a);
  13749. b = store.indexOf(b);
  13750. if (a < b) {
  13751. return -1;
  13752. } else if (a > b) {
  13753. return 1;
  13754. }
  13755. return 0;
  13756. });
  13757. },
  13758. onTopBtnClick: function() {
  13759. var list = this.toField.boundList,
  13760. store = list.getStore(),
  13761. selected = this.getSelections(list);
  13762. store.suspendEvents();
  13763. store.remove(selected, true);
  13764. store.insert(0, selected);
  13765. store.resumeEvents();
  13766. list.refresh();
  13767. this.syncValue();
  13768. list.getSelectionModel().select(selected);
  13769. },
  13770. onBottomBtnClick: function() {
  13771. var list = this.toField.boundList,
  13772. store = list.getStore(),
  13773. selected = this.getSelections(list);
  13774. store.suspendEvents();
  13775. store.remove(selected, true);
  13776. store.add(selected);
  13777. store.resumeEvents();
  13778. list.refresh();
  13779. this.syncValue();
  13780. list.getSelectionModel().select(selected);
  13781. },
  13782. onUpBtnClick: function() {
  13783. var list = this.toField.boundList,
  13784. store = list.getStore(),
  13785. selected = this.getSelections(list),
  13786. rec,
  13787. i = 0,
  13788. len = selected.length,
  13789. index = 0;
  13790. // Move each selection up by one place if possible
  13791. store.suspendEvents();
  13792. for (; i < len; ++i , index++) {
  13793. rec = selected[i];
  13794. index = Math.max(index, store.indexOf(rec) - 1);
  13795. store.remove(rec, true);
  13796. store.insert(index, rec);
  13797. }
  13798. store.resumeEvents();
  13799. list.refresh();
  13800. this.syncValue();
  13801. list.getSelectionModel().select(selected);
  13802. },
  13803. onDownBtnClick: function() {
  13804. var list = this.toField.boundList,
  13805. store = list.getStore(),
  13806. selected = this.getSelections(list),
  13807. rec,
  13808. i = selected.length - 1,
  13809. index = store.getCount() - 1;
  13810. // Move each selection down by one place if possible
  13811. store.suspendEvents();
  13812. for (; i > -1; --i , index--) {
  13813. rec = selected[i];
  13814. index = Math.min(index, store.indexOf(rec) + 1);
  13815. store.remove(rec, true);
  13816. store.insert(index, rec);
  13817. }
  13818. store.resumeEvents();
  13819. list.refresh();
  13820. this.syncValue();
  13821. list.getSelectionModel().select(selected);
  13822. },
  13823. onAddBtnClick: function() {
  13824. var me = this,
  13825. selected = me.getSelections(me.fromField.boundList);
  13826. me.moveRec(true, selected);
  13827. me.toField.boundList.getSelectionModel().select(selected);
  13828. },
  13829. onRemoveBtnClick: function() {
  13830. var me = this,
  13831. selected = me.getSelections(me.toField.boundList);
  13832. me.moveRec(false, selected);
  13833. me.fromField.boundList.getSelectionModel().select(selected);
  13834. },
  13835. moveRec: function(add, recs) {
  13836. var me = this,
  13837. fromField = me.fromField,
  13838. toField = me.toField,
  13839. fromStore = add ? fromField.store : toField.store,
  13840. toStore = add ? toField.store : fromField.store;
  13841. fromStore.suspendEvents();
  13842. toStore.suspendEvents();
  13843. fromStore.remove(recs);
  13844. toStore.add(recs);
  13845. fromStore.resumeEvents();
  13846. toStore.resumeEvents();
  13847. // If the list item was focused when moved (e.g. via double-click)
  13848. // then removing it will cause the focus to be thrown back to the
  13849. // document body. Which might disrupt things if ItemSelector is
  13850. // contained by a floating thingie like a Menu.
  13851. // Focusing the list itself will prevent that.
  13852. if (fromField.boundList.containsFocus) {
  13853. fromField.boundList.focus();
  13854. }
  13855. fromField.boundList.refresh();
  13856. toField.boundList.refresh();
  13857. me.syncValue();
  13858. },
  13859. // Synchronizes the submit value with the current state of the toStore
  13860. syncValue: function() {
  13861. var me = this;
  13862. me.mixins.field.setValue.call(me, me.setupValue(me.toField.store.getRange()));
  13863. },
  13864. onItemDblClick: function(view, rec) {
  13865. this.moveRec(view === this.fromField.boundList, rec);
  13866. },
  13867. setValue: function(value) {
  13868. var me = this,
  13869. fromField = me.fromField,
  13870. toField = me.toField,
  13871. fromStore = fromField.store,
  13872. toStore = toField.store,
  13873. selected;
  13874. // Wait for from store to be loaded
  13875. if (!me.fromStorePopulated) {
  13876. me.fromField.store.on({
  13877. load: Ext.Function.bind(me.setValue, me, [
  13878. value
  13879. ]),
  13880. single: true
  13881. });
  13882. return;
  13883. }
  13884. value = me.setupValue(value);
  13885. me.mixins.field.setValue.call(me, value);
  13886. selected = me.getRecordsForValue(value);
  13887. // Clear both left and right Stores.
  13888. // Both stores must not fire events during this process.
  13889. fromStore.suspendEvents();
  13890. toStore.suspendEvents();
  13891. fromStore.removeAll();
  13892. toStore.removeAll();
  13893. // Reset fromStore
  13894. me.populateFromStore(me.store);
  13895. // Copy selection across to toStore
  13896. Ext.Array.forEach(selected, function(rec) {
  13897. // In the from store, move it over
  13898. if (fromStore.indexOf(rec) > -1) {
  13899. fromStore.remove(rec);
  13900. }
  13901. toStore.add(rec);
  13902. });
  13903. // Stores may now fire events
  13904. fromStore.resumeEvents();
  13905. toStore.resumeEvents();
  13906. // Refresh both sides and then update the app layout
  13907. Ext.suspendLayouts();
  13908. fromField.boundList.refresh();
  13909. toField.boundList.refresh();
  13910. Ext.resumeLayouts(true);
  13911. },
  13912. onBindStore: function(store, initial) {
  13913. var me = this,
  13914. fromField = me.fromField,
  13915. toField = me.toField;
  13916. if (fromField) {
  13917. fromField.store.removeAll();
  13918. toField.store.removeAll();
  13919. if (store.autoCreated) {
  13920. fromField.resolveDisplayField();
  13921. toField.resolveDisplayField();
  13922. me.resolveDisplayField();
  13923. }
  13924. if (!Ext.isDefined(me.valueField)) {
  13925. me.valueField = me.displayField;
  13926. }
  13927. // Add everything to the from field as soon as the Store is loaded
  13928. if (store.getCount()) {
  13929. me.populateFromStore(store);
  13930. } else {
  13931. me.store.on('load', me.populateFromStore, me);
  13932. }
  13933. }
  13934. },
  13935. populateFromStore: function(store) {
  13936. var fromStore = this.fromField.store;
  13937. // Flag set when the fromStore has been loaded
  13938. this.fromStorePopulated = true;
  13939. fromStore.add(store.getRange());
  13940. // setValue waits for the from Store to be loaded
  13941. fromStore.fireEvent('load', fromStore);
  13942. },
  13943. onEnable: function() {
  13944. var me = this;
  13945. me.callParent();
  13946. me.fromField.enable();
  13947. me.toField.enable();
  13948. Ext.Array.forEach(me.query('[navBtn]'), function(btn) {
  13949. btn.enable();
  13950. });
  13951. },
  13952. onDisable: function() {
  13953. var me = this;
  13954. me.callParent();
  13955. me.fromField.disable();
  13956. me.toField.disable();
  13957. Ext.Array.forEach(me.query('[navBtn]'), function(btn) {
  13958. btn.disable();
  13959. });
  13960. },
  13961. doDestroy: function() {
  13962. this.bindStore(null);
  13963. this.callParent();
  13964. }
  13965. });
  13966. /**
  13967. *
  13968. */
  13969. Ext.define('Ext.ux.form.SearchField', {
  13970. extend: 'Ext.form.field.Text',
  13971. alias: 'widget.searchfield',
  13972. triggers: {
  13973. clear: {
  13974. weight: 0,
  13975. cls: Ext.baseCSSPrefix + 'form-clear-trigger',
  13976. hidden: true,
  13977. handler: 'onClearClick',
  13978. scope: 'this'
  13979. },
  13980. search: {
  13981. weight: 1,
  13982. cls: Ext.baseCSSPrefix + 'form-search-trigger',
  13983. handler: 'onSearchClick',
  13984. scope: 'this'
  13985. }
  13986. },
  13987. hasSearch: false,
  13988. paramName: 'query',
  13989. initComponent: function() {
  13990. var me = this,
  13991. store = me.store,
  13992. proxy;
  13993. me.callParent(arguments);
  13994. me.on('specialkey', function(f, e) {
  13995. if (e.getKey() === e.ENTER) {
  13996. me.onSearchClick();
  13997. }
  13998. });
  13999. if (!store || !store.isStore) {
  14000. store = me.store = Ext.data.StoreManager.lookup(store);
  14001. }
  14002. // We're going to use filtering
  14003. store.setRemoteFilter(true);
  14004. // Set up the proxy to encode the filter in the simplest way as a name/value pair
  14005. proxy = me.store.getProxy();
  14006. proxy.setFilterParam(me.paramName);
  14007. proxy.encodeFilters = function(filters) {
  14008. return filters[0].getValue();
  14009. };
  14010. },
  14011. onClearClick: function() {
  14012. var me = this,
  14013. activeFilter = me.activeFilter;
  14014. if (activeFilter) {
  14015. me.setValue('');
  14016. me.store.getFilters().remove(activeFilter);
  14017. me.activeFilter = null;
  14018. me.getTrigger('clear').hide();
  14019. me.updateLayout();
  14020. }
  14021. },
  14022. onSearchClick: function() {
  14023. var me = this,
  14024. value = me.getValue();
  14025. if (value.length > 0) {
  14026. // Param name is ignored here since we use custom encoding in the proxy.
  14027. // id is used by the Store to replace any previous filter
  14028. me.activeFilter = new Ext.util.Filter({
  14029. property: me.paramName,
  14030. value: value
  14031. });
  14032. me.store.getFilters().add(me.activeFilter);
  14033. me.getTrigger('clear').show();
  14034. me.updateLayout();
  14035. }
  14036. }
  14037. });
  14038. /**
  14039. * A small grid nested within a parent grid's row.
  14040. *
  14041. * See the [Kitchen Sink](http://dev.sencha.com/extjs/5.0.1/examples/kitchensink/#customer-grid)
  14042. * for example usage.
  14043. */
  14044. Ext.define('Ext.ux.grid.SubTable', {
  14045. extend: 'Ext.grid.plugin.RowExpander',
  14046. alias: 'plugin.subtable',
  14047. /* eslint-disable indent */
  14048. rowBodyTpl: [
  14049. '<table class="' + Ext.baseCSSPrefix + 'grid-subtable">',
  14050. '{%',
  14051. 'this.owner.renderTable(out, values);',
  14052. '%}',
  14053. '</table>'
  14054. ],
  14055. /* eslint-enable indent */
  14056. init: function(grid) {
  14057. var me = this,
  14058. columns = me.columns,
  14059. len, i, columnCfg;
  14060. me.callParent(arguments);
  14061. me.columns = [];
  14062. if (columns) {
  14063. for (i = 0 , len = columns.length; i < len; ++i) {
  14064. // Don't register with the component manager, we create them to use
  14065. // their rendering smarts, but don't want to treat them as real components
  14066. columnCfg = Ext.apply({
  14067. preventRegister: true
  14068. }, columns[i]);
  14069. columnCfg.xtype = columnCfg.xtype || 'gridcolumn';
  14070. me.columns.push(Ext.widget(columnCfg));
  14071. }
  14072. }
  14073. },
  14074. destroy: function() {
  14075. var columns = this.columns,
  14076. len, i;
  14077. if (columns) {
  14078. for (i = 0 , len = columns.length; i < len; ++i) {
  14079. columns[i].destroy();
  14080. }
  14081. }
  14082. this.columns = null;
  14083. this.callParent();
  14084. },
  14085. getRowBodyFeatureData: function(record, idx, rowValues) {
  14086. this.callParent(arguments);
  14087. rowValues.rowBodyCls += ' ' + Ext.baseCSSPrefix + 'grid-subtable-row';
  14088. },
  14089. renderTable: function(out, rowValues) {
  14090. var me = this,
  14091. columns = me.columns,
  14092. numColumns = columns.length,
  14093. associatedRecords = me.getAssociatedRecords(rowValues.record),
  14094. recCount = associatedRecords.length,
  14095. rec, column, i, j, value;
  14096. out.push('<thead>');
  14097. for (j = 0; j < numColumns; j++) {
  14098. out.push('<th class="' + Ext.baseCSSPrefix + 'grid-subtable-header">', columns[j].text, '</th>');
  14099. }
  14100. out.push('</thead><tbody>');
  14101. for (i = 0; i < recCount; i++) {
  14102. rec = associatedRecords[i];
  14103. out.push('<tr>');
  14104. for (j = 0; j < numColumns; j++) {
  14105. column = columns[j];
  14106. value = rec.get(column.dataIndex);
  14107. if (column.renderer && column.renderer.call) {
  14108. value = column.renderer.call(column.scope || me, value, {}, rec);
  14109. }
  14110. out.push('<td class="' + Ext.baseCSSPrefix + 'grid-subtable-cell"');
  14111. if (column.width != null) {
  14112. out.push(' style="width:' + column.width + 'px"');
  14113. }
  14114. out.push('><div class="' + Ext.baseCSSPrefix + 'grid-cell-inner">', value, '</div></td>');
  14115. }
  14116. out.push('</tr>');
  14117. }
  14118. out.push('</tbody>');
  14119. },
  14120. getRowBodyContentsFn: function(rowBodyTpl) {
  14121. var me = this;
  14122. return function(rowValues) {
  14123. rowBodyTpl.owner = me;
  14124. return rowBodyTpl.applyTemplate(rowValues);
  14125. };
  14126. },
  14127. getAssociatedRecords: function(record) {
  14128. return record[this.association]().getRange();
  14129. }
  14130. });
  14131. /**
  14132. * A Grid which creates itself from an existing HTML table element.
  14133. */
  14134. Ext.define('Ext.ux.grid.TransformGrid', {
  14135. extend: 'Ext.grid.Panel',
  14136. /**
  14137. * Creates the grid from HTML table element.
  14138. * @param {String/HTMLElement/Ext.Element} table The table element from which this grid
  14139. * will be created - The table MUST have some type of size defined for the grid to fill.
  14140. * The container will be automatically set to position relative if it isn't already.
  14141. * @param {Object} [config] A config object that sets properties on this grid and has two
  14142. * additional (optional) properties: fields and columns which allow for customizing data fields
  14143. * and columns for this grid.
  14144. */
  14145. constructor: function(table, config) {
  14146. config = Ext.apply({}, config);
  14147. table = this.table = Ext.get(table);
  14148. // eslint-disable-next-line vars-on-top
  14149. var configFields = config.fields || [],
  14150. configColumns = config.columns || [],
  14151. fields = [],
  14152. cols = [],
  14153. headers = table.query("thead th"),
  14154. i = 0,
  14155. len = headers.length,
  14156. data = table.dom,
  14157. width, height, col, text, name;
  14158. for (; i < len; ++i) {
  14159. col = headers[i];
  14160. text = col.innerHTML;
  14161. name = 'tcol-' + i;
  14162. fields.push(Ext.applyIf(configFields[i] || {}, {
  14163. name: name,
  14164. mapping: 'td:nth(' + (i + 1) + ')/@innerHTML'
  14165. }));
  14166. cols.push(Ext.applyIf(configColumns[i] || {}, {
  14167. text: text,
  14168. dataIndex: name,
  14169. width: col.offsetWidth,
  14170. tooltip: col.title,
  14171. sortable: true
  14172. }));
  14173. }
  14174. if (config.width) {
  14175. width = config.width;
  14176. } else {
  14177. width = table.getWidth() + 1;
  14178. }
  14179. if (config.height) {
  14180. height = config.height;
  14181. }
  14182. Ext.applyIf(config, {
  14183. store: {
  14184. data: data,
  14185. fields: fields,
  14186. proxy: {
  14187. type: 'memory',
  14188. reader: {
  14189. record: 'tbody tr',
  14190. type: 'xml'
  14191. }
  14192. }
  14193. },
  14194. columns: cols,
  14195. width: width,
  14196. height: height
  14197. });
  14198. this.callParent([
  14199. config
  14200. ]);
  14201. if (config.remove !== false) {
  14202. // Don't use table.remove() as that destroys the row/cell data in the table in
  14203. // IE6-7 so it cannot be read by the data reader.
  14204. data.parentNode.removeChild(data);
  14205. }
  14206. },
  14207. doDestroy: function() {
  14208. this.table.remove();
  14209. this.tabl = null;
  14210. this.callParent();
  14211. }
  14212. });
  14213. /**
  14214. * This plugin ensures that its associated grid or tree always has a selection record. The
  14215. * only exception is, of course, when there are no records in the store.
  14216. * @since 6.0.2
  14217. */
  14218. Ext.define('Ext.ux.grid.plugin.AutoSelector', {
  14219. extend: 'Ext.plugin.Abstract',
  14220. alias: 'plugin.gridautoselector',
  14221. config: {
  14222. store: null
  14223. },
  14224. init: function(grid) {
  14225. var me = this;
  14226. //<debug>
  14227. if (!grid.isXType('tablepanel')) {
  14228. Ext.raise('The gridautoselector plugin is designed only for grids and trees');
  14229. }
  14230. //</debug>
  14231. me.grid = grid;
  14232. me.watchGrid();
  14233. grid.on({
  14234. reconfigure: me.watchGrid,
  14235. scope: me
  14236. });
  14237. },
  14238. destroy: function() {
  14239. this.setStore(null);
  14240. this.grid = null;
  14241. this.callParent();
  14242. },
  14243. ensureSelection: function() {
  14244. var grid = this.grid,
  14245. store = grid.getStore(),
  14246. selection;
  14247. if (store.getCount()) {
  14248. selection = grid.getSelection();
  14249. if (!selection || !selection.length) {
  14250. grid.getSelectionModel().select(0);
  14251. }
  14252. }
  14253. },
  14254. watchGrid: function() {
  14255. this.setStore(this.grid.getStore());
  14256. this.ensureSelection();
  14257. },
  14258. updateStore: function(store) {
  14259. var me = this;
  14260. Ext.destroy(me.storeListeners);
  14261. me.storeListeners = store && store.on({
  14262. // We could go from 0 records to 1+ records... now we can select one!
  14263. add: me.ensureSelection,
  14264. // We might remove the selected record...
  14265. remove: me.ensureSelection,
  14266. destroyable: true,
  14267. scope: me
  14268. });
  14269. }
  14270. });
  14271. /**
  14272. * A simple grid-like layout for proportionally dividing container space and allocating it
  14273. * to each item. All items in this layout are given one or more percentage sizes and CSS
  14274. * `float:left` is used to provide the wrapping.
  14275. *
  14276. * To select which of the percentage sizes an item uses, this layout adds a viewport
  14277. * {@link #states size-dependent} class name to the container. The style sheet must
  14278. * provide the rules to select the desired size using the {@link #responsivecolumn-item}
  14279. * mixin.
  14280. *
  14281. * For example, a panel in a responsive column layout might add the following styles:
  14282. *
  14283. * .my-panel {
  14284. * // consume 50% of the available space inside the container by default
  14285. * @include responsivecolumn-item(50%);
  14286. *
  14287. * .x-responsivecolumn-small & {
  14288. * // consume 100% of available space in "small" mode
  14289. * // (viewport width < 1000 by default)
  14290. * @include responsivecolumn-item(100%);
  14291. * }
  14292. * }
  14293. *
  14294. * Alternatively, instead of targeting specific panels in CSS, you can create reusable
  14295. * classes:
  14296. *
  14297. * .big-50 {
  14298. * // consume 50% of the available space inside the container by default
  14299. * @include responsivecolumn-item(50%);
  14300. * }
  14301. *
  14302. * .x-responsivecolumn-small {
  14303. * > .small-100 {
  14304. * @include responsivecolumn-item(100%);
  14305. * }
  14306. * }
  14307. *
  14308. * These can be added to components in the layout using the `responsiveCls` config:
  14309. *
  14310. * items: [{
  14311. * xtype: 'my-panel',
  14312. *
  14313. * // Use 50% of space when viewport is "big" and 100% when viewport
  14314. * // is "small":
  14315. * responsiveCls: 'big-50 small-100'
  14316. * }]
  14317. *
  14318. * The `responsiveCls` config is provided by this layout to avoid overwriting classes
  14319. * specified using `cls` or other standard configs.
  14320. *
  14321. * Internally, this layout simply uses `float:left` and CSS `calc()` (except on IE8) to
  14322. * "flex" each item. The calculation is always based on a percentage with a spacing taken
  14323. * into account to separate the items from each other.
  14324. */
  14325. Ext.define('Ext.ux.layout.ResponsiveColumn', {
  14326. extend: 'Ext.layout.container.Auto',
  14327. alias: 'layout.responsivecolumn',
  14328. /**
  14329. * @cfg {Object} states
  14330. *
  14331. * A set of layout state names corresponding to viewport size thresholds. One of the
  14332. * states will be used to assign the responsive column CSS class to the container to
  14333. * trigger appropriate item sizing.
  14334. *
  14335. * For example:
  14336. *
  14337. * layout: {
  14338. * type: 'responsivecolumn',
  14339. * states: {
  14340. * small: 800,
  14341. * medium: 1200,
  14342. * large: 0
  14343. * }
  14344. * }
  14345. *
  14346. * Given the above set of responsive states, one of the following CSS classes will be
  14347. * added to the container:
  14348. *
  14349. * - `x-responsivecolumn-small` - If the viewport is <= 800px
  14350. * - `x-responsivecolumn-medium` - If the viewport is > 800px and <= 1200px
  14351. * - `x-responsivecolumn-large` - If the viewport is > 1200px
  14352. *
  14353. * For sake of efficiency these classes are based on the size of the browser viewport
  14354. * (the browser window) and not on the container size. As the size of the viewport
  14355. * changes, this layout will maintain the appropriate CSS class on the container which
  14356. * will then activate the appropriate CSS rules to size the child items.
  14357. */
  14358. states: {
  14359. small: 1000,
  14360. large: 0
  14361. },
  14362. _responsiveCls: Ext.baseCSSPrefix + 'responsivecolumn',
  14363. initLayout: function() {
  14364. this.innerCtCls += ' ' + this._responsiveCls;
  14365. this.callParent();
  14366. },
  14367. beginLayout: function(ownerContext) {
  14368. var me = this,
  14369. viewportWidth = Ext.Element.getViewportWidth(),
  14370. states = me.states,
  14371. activeThreshold = Infinity,
  14372. innerCt = me.innerCt,
  14373. currentState = me._currentState,
  14374. name, threshold, newState;
  14375. for (name in states) {
  14376. threshold = states[name] || Infinity;
  14377. if (viewportWidth <= threshold && threshold <= activeThreshold) {
  14378. activeThreshold = threshold;
  14379. newState = name;
  14380. }
  14381. }
  14382. if (newState !== currentState) {
  14383. innerCt.replaceCls(currentState, newState, me._responsiveCls);
  14384. me._currentState = newState;
  14385. }
  14386. me.callParent(arguments);
  14387. },
  14388. onAdd: function(item) {
  14389. var responsiveCls;
  14390. this.callParent([
  14391. item
  14392. ]);
  14393. responsiveCls = item.responsiveCls;
  14394. if (responsiveCls) {
  14395. item.addCls(responsiveCls);
  14396. }
  14397. }
  14398. }, function(Responsive) {
  14399. //--------------------------------------------------------------------------------------
  14400. // IE8 does not support CSS calc expressions, so we have to fallback to more traditional
  14401. // for of layout. This is very similar but much simpler than Column layout.
  14402. //
  14403. if (Ext.isIE8) {
  14404. Responsive.override({
  14405. responsiveSizePolicy: {
  14406. readsWidth: 0,
  14407. readsHeight: 0,
  14408. setsWidth: 1,
  14409. setsHeight: 0
  14410. },
  14411. setsItemSize: true,
  14412. calculateItems: function(ownerContext, containerSize) {
  14413. var me = this,
  14414. targetContext = ownerContext.targetContext,
  14415. items = ownerContext.childItems,
  14416. len = items.length,
  14417. gotWidth = containerSize.gotWidth,
  14418. contentWidth = containerSize.width,
  14419. i, itemContext, itemMarginWidth, itemWidth;
  14420. // No parallel measurement, cannot lay out boxes.
  14421. if (gotWidth === false) {
  14422. targetContext.domBlock(me, 'width');
  14423. return false;
  14424. }
  14425. if (!gotWidth) {
  14426. // gotWidth is undefined, which means we must be width shrink wrap.
  14427. // Cannot calculate item widths if we're shrink wrapping.
  14428. return true;
  14429. }
  14430. for (i = 0; i < len; ++i) {
  14431. itemContext = items[i];
  14432. // The mixin encodes these in background-position syles since it is
  14433. // unlikely a component will have a background-image.
  14434. itemWidth = parseInt(itemContext.el.getStyle('background-position-x'), 10);
  14435. itemMarginWidth = parseInt(itemContext.el.getStyle('background-position-y'), 10);
  14436. itemContext.setWidth((itemWidth / 100 * (contentWidth - itemMarginWidth)) - itemMarginWidth);
  14437. }
  14438. ownerContext.setContentWidth(contentWidth + ownerContext.paddingContext.getPaddingInfo().width);
  14439. return true;
  14440. },
  14441. getItemSizePolicy: function() {
  14442. return this.responsiveSizePolicy;
  14443. }
  14444. });
  14445. }
  14446. });
  14447. /**
  14448. * A {@link Ext.ux.statusbar.StatusBar} plugin that provides automatic error
  14449. * notification when the associated form contains validation errors.
  14450. */
  14451. Ext.define('Ext.ux.statusbar.ValidationStatus', {
  14452. extend: 'Ext.Component',
  14453. alias: 'plugin.validationstatus',
  14454. requires: [
  14455. 'Ext.util.MixedCollection'
  14456. ],
  14457. /**
  14458. * @cfg {String} errorIconCls
  14459. * The {@link Ext.ux.statusbar.StatusBar#iconCls iconCls} value to be applied
  14460. * to the status message when there is a validation error.
  14461. */
  14462. errorIconCls: 'x-status-error',
  14463. /**
  14464. * @cfg {String} errorListCls
  14465. * The css class to be used for the error list when there are validation errors.
  14466. */
  14467. errorListCls: 'x-status-error-list',
  14468. /**
  14469. * @cfg {String} validIconCls
  14470. * The {@link Ext.ux.statusbar.StatusBar#iconCls iconCls} value to be applied
  14471. * to the status message when the form validates.
  14472. */
  14473. validIconCls: 'x-status-valid',
  14474. /**
  14475. * @cfg {String} showText
  14476. * The {@link Ext.ux.statusbar.StatusBar#text text} value to be applied when
  14477. * there is a form validation error.
  14478. */
  14479. showText: 'The form has errors (click for details...)',
  14480. /**
  14481. * @cfg {String} hideText
  14482. * The {@link Ext.ux.statusbar.StatusBar#text text} value to display when
  14483. * the error list is displayed.
  14484. */
  14485. hideText: 'Click again to hide the error list',
  14486. /**
  14487. * @cfg {String} submitText
  14488. * The {@link Ext.ux.statusbar.StatusBar#text text} value to be applied when
  14489. * the form is being submitted.
  14490. */
  14491. submitText: 'Saving...',
  14492. /**
  14493. * @private
  14494. */
  14495. init: function(sb) {
  14496. var me = this;
  14497. me.statusBar = sb;
  14498. sb.on({
  14499. single: true,
  14500. scope: me,
  14501. render: me.onStatusbarRender
  14502. });
  14503. sb.on({
  14504. click: {
  14505. element: 'el',
  14506. fn: me.onStatusClick,
  14507. scope: me,
  14508. buffer: 200
  14509. }
  14510. });
  14511. },
  14512. onStatusbarRender: function(sb) {
  14513. var me = this,
  14514. startMonitor = function() {
  14515. me.monitor = true;
  14516. };
  14517. me.monitor = true;
  14518. me.errors = Ext.create('Ext.util.MixedCollection');
  14519. me.listAlign = (sb.statusAlign === 'right' ? 'br-tr?' : 'bl-tl?');
  14520. if (me.form) {
  14521. // Allow either an id, or a reference to be specified as the form name.
  14522. me.formPanel = Ext.getCmp(me.form) || me.statusBar.lookupController().lookupReference(me.form);
  14523. me.basicForm = me.formPanel.getForm();
  14524. me.startMonitoring();
  14525. me.basicForm.on({
  14526. beforeaction: function(f, action) {
  14527. if (action.type === 'submit') {
  14528. // Ignore monitoring while submitting otherwise the field validation
  14529. // events cause the status message to reset too early
  14530. me.monitor = false;
  14531. }
  14532. }
  14533. });
  14534. me.formPanel.on({
  14535. beforedestroy: me.destroy,
  14536. scope: me
  14537. });
  14538. me.basicForm.on('actioncomplete', startMonitor);
  14539. me.basicForm.on('actionfailed', startMonitor);
  14540. }
  14541. },
  14542. /**
  14543. * @private
  14544. */
  14545. startMonitoring: function() {
  14546. this.basicForm.getFields().each(function(f) {
  14547. f.on('validitychange', this.onFieldValidation, this);
  14548. }, this);
  14549. },
  14550. /**
  14551. * @private
  14552. */
  14553. stopMonitoring: function() {
  14554. var form = this.basicForm;
  14555. if (!form.destroyed) {
  14556. form.getFields().each(function(f) {
  14557. f.un('validitychange', this.onFieldValidation, this);
  14558. }, this);
  14559. }
  14560. },
  14561. doDestroy: function() {
  14562. Ext.destroy(this.msgEl);
  14563. this.stopMonitoring();
  14564. this.statusBar.statusEl.un('click', this.onStatusClick, this);
  14565. this.callParent();
  14566. },
  14567. /**
  14568. * @private
  14569. */
  14570. onFieldValidation: function(f, isValid) {
  14571. var me = this,
  14572. msg;
  14573. if (!me.monitor) {
  14574. return false;
  14575. }
  14576. msg = f.getErrors()[0];
  14577. if (msg) {
  14578. me.errors.add(f.id, {
  14579. field: f,
  14580. msg: msg
  14581. });
  14582. } else {
  14583. me.errors.removeAtKey(f.id);
  14584. }
  14585. this.updateErrorList();
  14586. if (me.errors.getCount() > 0) {
  14587. if (me.statusBar.getText() !== me.showText) {
  14588. me.statusBar.setStatus({
  14589. text: me.showText,
  14590. iconCls: me.errorIconCls
  14591. });
  14592. }
  14593. } else {
  14594. me.statusBar.clearStatus().setIcon(me.validIconCls);
  14595. }
  14596. },
  14597. /**
  14598. * @private
  14599. */
  14600. updateErrorList: function() {
  14601. var me = this,
  14602. msg,
  14603. msgEl = me.getMsgEl();
  14604. if (me.errors.getCount() > 0) {
  14605. msg = [
  14606. '<ul>'
  14607. ];
  14608. this.errors.each(function(err) {
  14609. msg.push('<li id="x-err-', err.field.id, '"><a href="#">', err.msg, '</a></li>');
  14610. });
  14611. msg.push('</ul>');
  14612. msgEl.update(msg.join(''));
  14613. } else {
  14614. msgEl.update('');
  14615. }
  14616. // reset msgEl size
  14617. msgEl.setSize('auto', 'auto');
  14618. },
  14619. /**
  14620. * @private
  14621. */
  14622. getMsgEl: function() {
  14623. var me = this,
  14624. msgEl = me.msgEl,
  14625. t;
  14626. if (!msgEl) {
  14627. msgEl = me.msgEl = Ext.DomHelper.append(Ext.getBody(), {
  14628. cls: me.errorListCls
  14629. }, true);
  14630. msgEl.hide();
  14631. msgEl.on('click', function(e) {
  14632. t = e.getTarget('li', 10, true);
  14633. if (t) {
  14634. Ext.getCmp(t.id.split('x-err-')[1]).focus();
  14635. me.hideErrors();
  14636. }
  14637. }, null, {
  14638. stopEvent: true
  14639. });
  14640. }
  14641. // prevent anchor click navigation
  14642. return msgEl;
  14643. },
  14644. /**
  14645. * @private
  14646. */
  14647. showErrors: function() {
  14648. var me = this;
  14649. me.updateErrorList();
  14650. me.getMsgEl().alignTo(me.statusBar.getEl(), me.listAlign).slideIn('b', {
  14651. duration: 300,
  14652. easing: 'easeOut'
  14653. });
  14654. me.statusBar.setText(me.hideText);
  14655. // hide if the user clicks directly into the form
  14656. me.formPanel.body.on('click', me.hideErrors, me, {
  14657. single: true
  14658. });
  14659. },
  14660. /**
  14661. * @private
  14662. */
  14663. hideErrors: function() {
  14664. var el = this.getMsgEl();
  14665. if (el.isVisible()) {
  14666. el.slideOut('b', {
  14667. duration: 300,
  14668. easing: 'easeIn'
  14669. });
  14670. this.statusBar.setText(this.showText);
  14671. }
  14672. this.formPanel.body.un('click', this.hideErrors, this);
  14673. },
  14674. /**
  14675. * @private
  14676. */
  14677. onStatusClick: function() {
  14678. if (this.getMsgEl().isVisible()) {
  14679. this.hideErrors();
  14680. } else if (this.errors.getCount() > 0) {
  14681. this.showErrors();
  14682. }
  14683. }
  14684. });