1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025902690279028902990309031903290339034903590369037903890399040904190429043904490459046904790489049905090519052905390549055905690579058905990609061906290639064906590669067906890699070907190729073907490759076907790789079908090819082908390849085908690879088908990909091909290939094909590969097909890999100910191029103910491059106910791089109911091119112911391149115911691179118911991209121912291239124912591269127912891299130913191329133913491359136913791389139914091419142914391449145914691479148914991509151915291539154915591569157915891599160916191629163916491659166916791689169917091719172917391749175917691779178917991809181918291839184918591869187918891899190919191929193919491959196919791989199920092019202920392049205920692079208920992109211921292139214921592169217921892199220922192229223922492259226922792289229923092319232923392349235923692379238923992409241924292439244924592469247924892499250925192529253925492559256925792589259926092619262926392649265926692679268926992709271927292739274927592769277927892799280928192829283928492859286928792889289929092919292929392949295929692979298929993009301930293039304930593069307930893099310931193129313931493159316931793189319932093219322932393249325932693279328932993309331933293339334933593369337933893399340934193429343934493459346934793489349935093519352935393549355935693579358935993609361936293639364936593669367936893699370937193729373937493759376937793789379938093819382938393849385938693879388938993909391939293939394939593969397939893999400940194029403940494059406940794089409941094119412941394149415941694179418941994209421942294239424942594269427942894299430943194329433943494359436943794389439944094419442944394449445944694479448944994509451945294539454945594569457945894599460946194629463946494659466946794689469947094719472947394749475947694779478947994809481948294839484948594869487948894899490949194929493949494959496949794989499950095019502950395049505950695079508950995109511951295139514951595169517951895199520952195229523952495259526952795289529953095319532953395349535953695379538953995409541954295439544954595469547954895499550955195529553955495559556955795589559956095619562956395649565956695679568956995709571957295739574957595769577957895799580958195829583958495859586958795889589959095919592959395949595959695979598959996009601960296039604960596069607960896099610961196129613961496159616961796189619962096219622962396249625962696279628962996309631963296339634963596369637963896399640964196429643964496459646964796489649965096519652965396549655965696579658965996609661966296639664966596669667966896699670967196729673967496759676967796789679968096819682968396849685968696879688968996909691969296939694969596969697969896999700970197029703970497059706970797089709971097119712971397149715971697179718971997209721972297239724972597269727972897299730973197329733973497359736973797389739974097419742974397449745974697479748974997509751975297539754975597569757975897599760976197629763976497659766976797689769977097719772977397749775977697779778977997809781978297839784978597869787978897899790979197929793979497959796979797989799980098019802980398049805980698079808980998109811981298139814981598169817981898199820982198229823982498259826982798289829983098319832983398349835983698379838983998409841984298439844984598469847984898499850985198529853985498559856985798589859986098619862986398649865986698679868986998709871987298739874987598769877987898799880988198829883988498859886988798889889989098919892989398949895989698979898989999009901990299039904990599069907990899099910991199129913991499159916991799189919992099219922992399249925992699279928992999309931993299339934993599369937993899399940994199429943994499459946994799489949995099519952995399549955995699579958995999609961996299639964996599669967996899699970997199729973997499759976997799789979998099819982998399849985998699879988998999909991999299939994999599969997999899991000010001100021000310004100051000610007100081000910010100111001210013100141001510016100171001810019100201002110022100231002410025100261002710028100291003010031100321003310034100351003610037100381003910040100411004210043100441004510046100471004810049100501005110052100531005410055100561005710058100591006010061100621006310064100651006610067100681006910070100711007210073100741007510076100771007810079100801008110082100831008410085100861008710088100891009010091100921009310094100951009610097100981009910100101011010210103101041010510106101071010810109101101011110112101131011410115101161011710118101191012010121101221012310124101251012610127101281012910130101311013210133101341013510136101371013810139101401014110142101431014410145101461014710148101491015010151101521015310154101551015610157101581015910160101611016210163101641016510166101671016810169101701017110172101731017410175101761017710178101791018010181101821018310184101851018610187101881018910190101911019210193101941019510196101971019810199102001020110202102031020410205102061020710208102091021010211102121021310214102151021610217102181021910220102211022210223102241022510226102271022810229102301023110232102331023410235102361023710238102391024010241102421024310244102451024610247102481024910250102511025210253102541025510256102571025810259102601026110262102631026410265102661026710268102691027010271102721027310274102751027610277102781027910280102811028210283102841028510286102871028810289102901029110292102931029410295102961029710298102991030010301103021030310304103051030610307103081030910310103111031210313103141031510316103171031810319103201032110322103231032410325103261032710328103291033010331103321033310334103351033610337103381033910340103411034210343103441034510346103471034810349103501035110352103531035410355103561035710358103591036010361103621036310364103651036610367103681036910370103711037210373103741037510376103771037810379103801038110382103831038410385103861038710388103891039010391103921039310394103951039610397103981039910400104011040210403104041040510406104071040810409104101041110412104131041410415104161041710418104191042010421104221042310424104251042610427104281042910430104311043210433104341043510436104371043810439104401044110442104431044410445104461044710448104491045010451104521045310454104551045610457104581045910460104611046210463104641046510466104671046810469104701047110472104731047410475104761047710478104791048010481104821048310484104851048610487104881048910490104911049210493104941049510496104971049810499105001050110502105031050410505105061050710508105091051010511105121051310514105151051610517105181051910520105211052210523105241052510526105271052810529105301053110532105331053410535105361053710538105391054010541105421054310544105451054610547105481054910550105511055210553105541055510556105571055810559105601056110562105631056410565105661056710568105691057010571105721057310574105751057610577105781057910580105811058210583105841058510586105871058810589105901059110592105931059410595105961059710598105991060010601106021060310604106051060610607106081060910610106111061210613106141061510616106171061810619106201062110622106231062410625106261062710628106291063010631106321063310634106351063610637106381063910640106411064210643106441064510646106471064810649106501065110652106531065410655106561065710658106591066010661106621066310664106651066610667106681066910670106711067210673106741067510676106771067810679106801068110682106831068410685106861068710688106891069010691106921069310694106951069610697106981069910700107011070210703107041070510706107071070810709107101071110712107131071410715107161071710718107191072010721107221072310724107251072610727107281072910730107311073210733107341073510736107371073810739107401074110742107431074410745107461074710748107491075010751107521075310754107551075610757107581075910760107611076210763107641076510766107671076810769107701077110772107731077410775107761077710778107791078010781107821078310784107851078610787107881078910790107911079210793107941079510796107971079810799108001080110802108031080410805108061080710808108091081010811108121081310814108151081610817108181081910820108211082210823108241082510826108271082810829108301083110832108331083410835108361083710838108391084010841108421084310844108451084610847108481084910850108511085210853108541085510856108571085810859108601086110862108631086410865108661086710868108691087010871108721087310874108751087610877108781087910880108811088210883108841088510886108871088810889108901089110892108931089410895108961089710898108991090010901109021090310904109051090610907109081090910910109111091210913109141091510916109171091810919109201092110922109231092410925109261092710928109291093010931109321093310934109351093610937109381093910940109411094210943109441094510946109471094810949109501095110952109531095410955109561095710958109591096010961109621096310964109651096610967109681096910970109711097210973109741097510976109771097810979109801098110982109831098410985109861098710988109891099010991109921099310994109951099610997109981099911000110011100211003110041100511006110071100811009110101101111012110131101411015110161101711018110191102011021110221102311024110251102611027110281102911030110311103211033110341103511036110371103811039110401104111042110431104411045110461104711048110491105011051110521105311054110551105611057110581105911060110611106211063110641106511066110671106811069110701107111072110731107411075110761107711078110791108011081110821108311084110851108611087110881108911090110911109211093110941109511096110971109811099111001110111102111031110411105111061110711108111091111011111111121111311114111151111611117111181111911120111211112211123111241112511126111271112811129111301113111132111331113411135111361113711138111391114011141111421114311144111451114611147111481114911150111511115211153111541115511156111571115811159111601116111162111631116411165111661116711168111691117011171111721117311174111751117611177111781117911180111811118211183111841118511186111871118811189111901119111192111931119411195111961119711198111991120011201112021120311204112051120611207112081120911210112111121211213112141121511216112171121811219112201122111222112231122411225112261122711228112291123011231112321123311234112351123611237112381123911240112411124211243112441124511246112471124811249112501125111252112531125411255112561125711258112591126011261112621126311264112651126611267112681126911270112711127211273112741127511276112771127811279112801128111282112831128411285112861128711288112891129011291112921129311294112951129611297112981129911300113011130211303113041130511306113071130811309113101131111312113131131411315113161131711318113191132011321113221132311324113251132611327113281132911330113311133211333113341133511336113371133811339113401134111342113431134411345113461134711348113491135011351113521135311354113551135611357113581135911360113611136211363113641136511366113671136811369113701137111372113731137411375113761137711378113791138011381113821138311384113851138611387113881138911390113911139211393113941139511396113971139811399114001140111402114031140411405114061140711408114091141011411114121141311414114151141611417114181141911420114211142211423114241142511426114271142811429114301143111432114331143411435114361143711438114391144011441114421144311444114451144611447114481144911450114511145211453114541145511456114571145811459114601146111462114631146411465114661146711468114691147011471114721147311474114751147611477114781147911480114811148211483114841148511486114871148811489114901149111492114931149411495114961149711498114991150011501115021150311504115051150611507115081150911510115111151211513115141151511516115171151811519115201152111522115231152411525115261152711528115291153011531115321153311534115351153611537115381153911540115411154211543115441154511546115471154811549115501155111552115531155411555115561155711558115591156011561115621156311564115651156611567115681156911570115711157211573115741157511576115771157811579115801158111582115831158411585115861158711588115891159011591115921159311594115951159611597115981159911600116011160211603116041160511606116071160811609116101161111612116131161411615116161161711618116191162011621116221162311624116251162611627116281162911630116311163211633116341163511636116371163811639116401164111642116431164411645116461164711648116491165011651116521165311654116551165611657116581165911660116611166211663116641166511666116671166811669116701167111672116731167411675116761167711678116791168011681116821168311684116851168611687116881168911690116911169211693116941169511696116971169811699117001170111702117031170411705117061170711708117091171011711117121171311714117151171611717117181171911720117211172211723117241172511726117271172811729117301173111732117331173411735117361173711738117391174011741117421174311744117451174611747117481174911750117511175211753117541175511756117571175811759117601176111762117631176411765117661176711768117691177011771117721177311774117751177611777117781177911780117811178211783117841178511786117871178811789117901179111792117931179411795117961179711798117991180011801118021180311804118051180611807118081180911810118111181211813118141181511816118171181811819118201182111822118231182411825118261182711828118291183011831118321183311834118351183611837118381183911840118411184211843118441184511846118471184811849118501185111852118531185411855118561185711858118591186011861118621186311864118651186611867118681186911870118711187211873118741187511876118771187811879118801188111882118831188411885118861188711888118891189011891118921189311894118951189611897118981189911900119011190211903119041190511906119071190811909119101191111912119131191411915119161191711918119191192011921119221192311924119251192611927119281192911930119311193211933119341193511936119371193811939119401194111942119431194411945119461194711948119491195011951119521195311954119551195611957119581195911960119611196211963119641196511966119671196811969119701197111972119731197411975119761197711978119791198011981119821198311984119851198611987119881198911990119911199211993119941199511996119971199811999120001200112002120031200412005120061200712008120091201012011120121201312014120151201612017120181201912020120211202212023120241202512026120271202812029120301203112032120331203412035120361203712038120391204012041120421204312044120451204612047120481204912050120511205212053120541205512056120571205812059120601206112062120631206412065120661206712068120691207012071120721207312074120751207612077120781207912080120811208212083120841208512086120871208812089120901209112092120931209412095120961209712098120991210012101121021210312104121051210612107121081210912110121111211212113121141211512116121171211812119121201212112122121231212412125121261212712128121291213012131121321213312134121351213612137121381213912140121411214212143121441214512146121471214812149121501215112152121531215412155121561215712158121591216012161121621216312164121651216612167121681216912170121711217212173121741217512176121771217812179121801218112182121831218412185121861218712188121891219012191121921219312194121951219612197121981219912200122011220212203122041220512206122071220812209122101221112212122131221412215122161221712218122191222012221122221222312224122251222612227122281222912230122311223212233122341223512236122371223812239122401224112242122431224412245122461224712248122491225012251122521225312254122551225612257122581225912260122611226212263122641226512266122671226812269122701227112272122731227412275122761227712278122791228012281122821228312284122851228612287122881228912290122911229212293122941229512296122971229812299123001230112302123031230412305123061230712308123091231012311123121231312314123151231612317123181231912320123211232212323123241232512326123271232812329123301233112332123331233412335123361233712338123391234012341123421234312344123451234612347123481234912350123511235212353123541235512356123571235812359123601236112362123631236412365123661236712368123691237012371123721237312374123751237612377123781237912380123811238212383123841238512386123871238812389123901239112392123931239412395123961239712398123991240012401124021240312404124051240612407124081240912410124111241212413124141241512416124171241812419124201242112422124231242412425124261242712428124291243012431124321243312434124351243612437124381243912440124411244212443124441244512446124471244812449124501245112452124531245412455124561245712458124591246012461124621246312464124651246612467124681246912470124711247212473124741247512476124771247812479124801248112482124831248412485124861248712488124891249012491124921249312494124951249612497124981249912500125011250212503125041250512506125071250812509125101251112512125131251412515125161251712518125191252012521125221252312524125251252612527125281252912530125311253212533125341253512536125371253812539125401254112542125431254412545125461254712548125491255012551125521255312554125551255612557125581255912560125611256212563125641256512566125671256812569125701257112572125731257412575125761257712578125791258012581125821258312584125851258612587125881258912590125911259212593125941259512596125971259812599126001260112602126031260412605126061260712608126091261012611126121261312614126151261612617126181261912620126211262212623126241262512626126271262812629126301263112632126331263412635126361263712638126391264012641126421264312644126451264612647126481264912650126511265212653126541265512656126571265812659126601266112662126631266412665126661266712668126691267012671126721267312674126751267612677126781267912680126811268212683126841268512686126871268812689126901269112692126931269412695126961269712698126991270012701127021270312704127051270612707127081270912710127111271212713127141271512716127171271812719127201272112722127231272412725127261272712728127291273012731127321273312734127351273612737127381273912740127411274212743127441274512746127471274812749127501275112752127531275412755127561275712758127591276012761127621276312764127651276612767127681276912770127711277212773127741277512776127771277812779127801278112782127831278412785127861278712788127891279012791127921279312794127951279612797127981279912800128011280212803128041280512806128071280812809128101281112812128131281412815128161281712818128191282012821128221282312824128251282612827128281282912830128311283212833128341283512836128371283812839128401284112842128431284412845128461284712848128491285012851128521285312854128551285612857128581285912860128611286212863128641286512866128671286812869128701287112872128731287412875128761287712878128791288012881128821288312884128851288612887128881288912890128911289212893128941289512896128971289812899129001290112902129031290412905129061290712908129091291012911129121291312914129151291612917129181291912920129211292212923129241292512926129271292812929129301293112932129331293412935129361293712938129391294012941129421294312944129451294612947129481294912950129511295212953129541295512956129571295812959129601296112962129631296412965129661296712968129691297012971129721297312974129751297612977129781297912980129811298212983129841298512986129871298812989129901299112992129931299412995129961299712998129991300013001130021300313004130051300613007130081300913010130111301213013130141301513016130171301813019130201302113022130231302413025130261302713028130291303013031130321303313034130351303613037130381303913040130411304213043130441304513046130471304813049130501305113052130531305413055130561305713058130591306013061130621306313064130651306613067130681306913070130711307213073130741307513076130771307813079130801308113082130831308413085130861308713088130891309013091130921309313094130951309613097130981309913100131011310213103131041310513106131071310813109131101311113112131131311413115131161311713118131191312013121131221312313124131251312613127131281312913130131311313213133131341313513136131371313813139131401314113142131431314413145131461314713148131491315013151131521315313154131551315613157131581315913160131611316213163131641316513166131671316813169131701317113172131731317413175131761317713178131791318013181131821318313184131851318613187131881318913190131911319213193131941319513196131971319813199132001320113202132031320413205132061320713208132091321013211132121321313214132151321613217132181321913220132211322213223132241322513226132271322813229132301323113232132331323413235132361323713238132391324013241132421324313244132451324613247132481324913250132511325213253132541325513256132571325813259132601326113262132631326413265132661326713268132691327013271132721327313274132751327613277132781327913280132811328213283132841328513286132871328813289132901329113292132931329413295132961329713298132991330013301133021330313304133051330613307133081330913310133111331213313133141331513316133171331813319133201332113322133231332413325133261332713328133291333013331133321333313334133351333613337133381333913340133411334213343133441334513346133471334813349133501335113352133531335413355133561335713358133591336013361133621336313364133651336613367133681336913370133711337213373133741337513376133771337813379133801338113382133831338413385133861338713388133891339013391133921339313394133951339613397133981339913400134011340213403134041340513406134071340813409134101341113412134131341413415134161341713418134191342013421134221342313424134251342613427134281342913430134311343213433134341343513436134371343813439134401344113442134431344413445134461344713448134491345013451134521345313454134551345613457134581345913460134611346213463134641346513466134671346813469134701347113472134731347413475134761347713478134791348013481134821348313484134851348613487134881348913490134911349213493134941349513496134971349813499135001350113502135031350413505135061350713508135091351013511135121351313514135151351613517135181351913520135211352213523135241352513526135271352813529135301353113532135331353413535135361353713538135391354013541135421354313544135451354613547135481354913550135511355213553135541355513556135571355813559135601356113562135631356413565135661356713568135691357013571135721357313574135751357613577135781357913580135811358213583135841358513586135871358813589135901359113592135931359413595135961359713598135991360013601136021360313604136051360613607136081360913610136111361213613136141361513616136171361813619136201362113622136231362413625136261362713628136291363013631136321363313634136351363613637136381363913640136411364213643136441364513646136471364813649136501365113652136531365413655136561365713658136591366013661136621366313664136651366613667136681366913670136711367213673136741367513676136771367813679136801368113682136831368413685136861368713688136891369013691136921369313694136951369613697136981369913700137011370213703137041370513706137071370813709137101371113712137131371413715137161371713718137191372013721137221372313724137251372613727137281372913730137311373213733137341373513736137371373813739137401374113742137431374413745137461374713748137491375013751137521375313754137551375613757137581375913760137611376213763137641376513766137671376813769137701377113772137731377413775137761377713778137791378013781137821378313784137851378613787137881378913790137911379213793137941379513796137971379813799138001380113802138031380413805138061380713808138091381013811138121381313814138151381613817138181381913820138211382213823138241382513826138271382813829138301383113832138331383413835138361383713838138391384013841138421384313844138451384613847138481384913850138511385213853138541385513856138571385813859138601386113862138631386413865138661386713868138691387013871138721387313874138751387613877138781387913880138811388213883138841388513886138871388813889138901389113892138931389413895138961389713898138991390013901139021390313904139051390613907139081390913910139111391213913139141391513916139171391813919139201392113922139231392413925139261392713928139291393013931139321393313934139351393613937139381393913940139411394213943139441394513946139471394813949139501395113952139531395413955139561395713958139591396013961139621396313964139651396613967139681396913970139711397213973139741397513976139771397813979139801398113982139831398413985139861398713988139891399013991139921399313994139951399613997139981399914000140011400214003140041400514006140071400814009140101401114012140131401414015140161401714018140191402014021140221402314024140251402614027140281402914030140311403214033140341403514036140371403814039140401404114042140431404414045140461404714048140491405014051140521405314054140551405614057140581405914060140611406214063140641406514066140671406814069140701407114072140731407414075140761407714078140791408014081140821408314084140851408614087140881408914090140911409214093140941409514096140971409814099141001410114102141031410414105141061410714108141091411014111141121411314114141151411614117141181411914120141211412214123141241412514126141271412814129141301413114132141331413414135141361413714138141391414014141141421414314144141451414614147141481414914150141511415214153141541415514156141571415814159141601416114162141631416414165141661416714168141691417014171141721417314174141751417614177141781417914180141811418214183141841418514186141871418814189141901419114192141931419414195141961419714198141991420014201142021420314204142051420614207142081420914210142111421214213142141421514216142171421814219142201422114222142231422414225142261422714228142291423014231142321423314234142351423614237142381423914240142411424214243142441424514246142471424814249142501425114252142531425414255142561425714258142591426014261142621426314264142651426614267142681426914270142711427214273142741427514276142771427814279142801428114282142831428414285142861428714288142891429014291142921429314294142951429614297142981429914300143011430214303143041430514306143071430814309143101431114312143131431414315143161431714318143191432014321143221432314324143251432614327143281432914330143311433214333143341433514336143371433814339143401434114342143431434414345143461434714348143491435014351143521435314354143551435614357143581435914360143611436214363143641436514366143671436814369143701437114372143731437414375143761437714378143791438014381143821438314384143851438614387143881438914390143911439214393143941439514396143971439814399144001440114402144031440414405144061440714408144091441014411144121441314414144151441614417144181441914420144211442214423144241442514426144271442814429144301443114432144331443414435144361443714438144391444014441144421444314444144451444614447144481444914450144511445214453144541445514456144571445814459144601446114462144631446414465144661446714468144691447014471144721447314474144751447614477144781447914480144811448214483144841448514486144871448814489144901449114492144931449414495144961449714498144991450014501145021450314504145051450614507145081450914510145111451214513145141451514516145171451814519145201452114522145231452414525145261452714528145291453014531145321453314534145351453614537145381453914540145411454214543145441454514546145471454814549145501455114552145531455414555145561455714558145591456014561145621456314564145651456614567145681456914570145711457214573145741457514576145771457814579145801458114582145831458414585145861458714588145891459014591145921459314594145951459614597145981459914600146011460214603146041460514606146071460814609146101461114612146131461414615146161461714618146191462014621146221462314624146251462614627146281462914630146311463214633146341463514636146371463814639146401464114642146431464414645146461464714648146491465014651146521465314654146551465614657146581465914660146611466214663146641466514666146671466814669146701467114672146731467414675146761467714678146791468014681146821468314684146851468614687146881468914690146911469214693146941469514696146971469814699147001470114702147031470414705147061470714708147091471014711147121471314714147151471614717147181471914720147211472214723147241472514726147271472814729147301473114732147331473414735147361473714738147391474014741147421474314744147451474614747147481474914750147511475214753147541475514756147571475814759147601476114762147631476414765147661476714768 |
- Ext.define(null, {
- override: 'Ext.ux.gauge.needle.Abstract',
- compatibility: Ext.isIE10p,
- setTransform: function(centerX, centerY, rotation) {
- var needleGroup = this.getNeedleGroup();
- this.callParent([
- centerX,
- centerY,
- rotation
- ]);
- needleGroup.set({
- transform: getComputedStyle(needleGroup.dom).getPropertyValue('transform')
- });
- },
- updateStyle: function(style) {
- var pathElement;
- this.callParent([
- style
- ]);
- if (Ext.isObject(style) && 'transform' in style) {
- pathElement = this.getNeedlePath();
- pathElement.set({
- transform: getComputedStyle(pathElement.dom).getPropertyValue('transform')
- });
- }
- }
- });
- /**
- * This is a base class for more advanced "simlets" (simulated servers). A simlet is asked
- * to provide a response given a {@link Ext.ux.ajax.SimXhr} instance.
- */
- Ext.define('Ext.ux.ajax.Simlet', function() {
- var urlRegex = /([^?#]*)(#.*)?$/,
- dateRegex = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/,
- intRegex = /^[+-]?\d+$/,
- floatRegex = /^[+-]?\d+\.\d+$/;
- function parseParamValue(value) {
- var m;
- if (Ext.isDefined(value)) {
- value = decodeURIComponent(value);
- if (intRegex.test(value)) {
- value = parseInt(value, 10);
- } else if (floatRegex.test(value)) {
- value = parseFloat(value);
- } else if (!!(m = dateRegex.exec(value))) {
- value = new Date(Date.UTC(+m[1], +m[2] - 1, +m[3], +m[4], +m[5], +m[6]));
- }
- }
- return value;
- }
- return {
- alias: 'simlet.basic',
- isSimlet: true,
- responseProps: [
- 'responseText',
- 'responseXML',
- 'status',
- 'statusText',
- 'responseHeaders'
- ],
- /**
- * @cfg {String/Function} responseText
- */
- /**
- * @cfg {String/Function} responseXML
- */
- /**
- * @cfg {Object/Function} responseHeaders
- */
- /**
- * @cfg {Number/Function} status
- */
- status: 200,
- /**
- * @cfg {String/Function} statusText
- */
- statusText: 'OK',
- constructor: function(config) {
- Ext.apply(this, config);
- },
- doGet: function(ctx) {
- return this.handleRequest(ctx);
- },
- doPost: function(ctx) {
- return this.handleRequest(ctx);
- },
- doRedirect: function(ctx) {
- return false;
- },
- doDelete: function(ctx) {
- var me = this,
- xhr = ctx.xhr,
- records = xhr.options.records;
- me.removeFromData(ctx, records);
- },
- /**
- * Performs the action requested by the given XHR and returns an object to be applied
- * on to the XHR (containing `status`, `responseText`, etc.). For the most part,
- * this is delegated to `doMethod` methods on this class, such as `doGet`.
- *
- * @param {Ext.ux.ajax.SimXhr} xhr The simulated XMLHttpRequest instance.
- * @return {Object} The response properties to add to the XMLHttpRequest.
- */
- exec: function(xhr) {
- var me = this,
- ret = {},
- method = 'do' + Ext.String.capitalize(xhr.method.toLowerCase()),
- // doGet
- fn = me[method];
- if (fn) {
- ret = fn.call(me, me.getCtx(xhr.method, xhr.url, xhr));
- } else {
- ret = {
- status: 405,
- statusText: 'Method Not Allowed'
- };
- }
- return ret;
- },
- getCtx: function(method, url, xhr) {
- return {
- method: method,
- params: this.parseQueryString(url),
- url: url,
- xhr: xhr
- };
- },
- handleRequest: function(ctx) {
- var me = this,
- ret = {},
- val;
- Ext.Array.forEach(me.responseProps, function(prop) {
- if (prop in me) {
- val = me[prop];
- if (Ext.isFunction(val)) {
- val = val.call(me, ctx);
- }
- ret[prop] = val;
- }
- });
- return ret;
- },
- openRequest: function(method, url, options, async) {
- var ctx = this.getCtx(method, url),
- redirect = this.doRedirect(ctx),
- xhr;
- if (options.action === 'destroy') {
- method = 'delete';
- }
- if (redirect) {
- xhr = redirect;
- } else {
- xhr = new Ext.ux.ajax.SimXhr({
- mgr: this.manager,
- simlet: this,
- options: options
- });
- xhr.open(method, url, async);
- }
- return xhr;
- },
- parseQueryString: function(str) {
- var m = urlRegex.exec(str),
- ret = {},
- key, value, pair, parts, i, n;
- if (m && m[1]) {
- parts = m[1].split('&');
- for (i = 0 , n = parts.length; i < n; ++i) {
- if ((pair = parts[i].split('='))[0]) {
- key = decodeURIComponent(pair.shift());
- value = parseParamValue((pair.length > 1) ? pair.join('=') : pair[0]);
- if (!(key in ret)) {
- ret[key] = value;
- } else if (Ext.isArray(ret[key])) {
- ret[key].push(value);
- } else {
- ret[key] = [
- ret[key],
- value
- ];
- }
- }
- }
- }
- return ret;
- },
- redirect: function(method, url, params) {
- switch (arguments.length) {
- case 2:
- if (typeof url === 'string') {
- break;
- };
- params = url;
- // fall...
- // eslint-disable-next-line no-fallthrough
- case 1:
- url = method;
- method = 'GET';
- break;
- }
- if (params) {
- url = Ext.urlAppend(url, Ext.Object.toQueryString(params));
- }
- return this.manager.openRequest(method, url);
- },
- removeFromData: function(ctx, records) {
- var me = this,
- data = me.getData(ctx),
- model = (ctx.xhr.options.proxy && ctx.xhr.options.proxy.getModel()) || {},
- idProperty = model.idProperty || 'id',
- i;
- Ext.each(records, function(record) {
- var id = record.get(idProperty);
- for (i = data.length; i-- > 0; ) {
- if (data[i][idProperty] === id) {
- me.deleteRecord(i);
- break;
- }
- }
- });
- }
- };
- }());
- /**
- * This base class is used to handle data preparation (e.g., sorting, filtering and
- * group summary).
- */
- Ext.define('Ext.ux.ajax.DataSimlet', function() {
- function makeSortFn(def, cmp) {
- var order = def.direction,
- sign = (order && order.toUpperCase() === 'DESC') ? -1 : 1;
- return function(leftRec, rightRec) {
- var lhs = leftRec[def.property],
- rhs = rightRec[def.property],
- c = (lhs < rhs) ? -1 : ((rhs < lhs) ? 1 : 0);
- if (c || !cmp) {
- return c * sign;
- }
- return cmp(leftRec, rightRec);
- };
- }
- function makeSortFns(defs, cmp) {
- var sortFn, i;
- for (sortFn = cmp , i = defs && defs.length; i; ) {
- sortFn = makeSortFn(defs[--i], sortFn);
- }
- return sortFn;
- }
- return {
- extend: 'Ext.ux.ajax.Simlet',
- buildNodes: function(node, path) {
- var me = this,
- nodeData = {
- data: []
- },
- len = node.length,
- children, i, child, name;
- me.nodes[path] = nodeData;
- for (i = 0; i < len; ++i) {
- nodeData.data.push(child = node[i]);
- name = child.text || child.title;
- child.id = path ? path + '/' + name : name;
- children = child.children;
- if (!(child.leaf = !children)) {
- delete child.children;
- me.buildNodes(children, child.id);
- }
- }
- },
- deleteRecord: function(pos) {
- if (this.data && typeof this.data !== 'function') {
- Ext.Array.removeAt(this.data, pos);
- }
- },
- fixTree: function(ctx, tree) {
- var me = this,
- node = ctx.params.node,
- nodes;
- if (!(nodes = me.nodes)) {
- me.nodes = nodes = {};
- me.buildNodes(tree, '');
- }
- node = nodes[node];
- if (node) {
- if (me.node) {
- me.node.sortedData = me.sortedData;
- me.node.currentOrder = me.currentOrder;
- }
- me.node = node;
- me.data = node.data;
- me.sortedData = node.sortedData;
- me.currentOrder = node.currentOrder;
- } else {
- me.data = null;
- }
- },
- getData: function(ctx) {
- var me = this,
- params = ctx.params,
- order = (params.filter || '') + (params.group || '') + '-' + (params.sort || '') + '-' + (params.dir || ''),
- tree = me.tree,
- dynamicData, data, fields, sortFn, filters;
- if (tree) {
- me.fixTree(ctx, tree);
- }
- data = me.data;
- if (typeof data === 'function') {
- dynamicData = true;
- data = data.call(this, ctx);
- }
- // If order is '--' then it means we had no order passed, due to the string concat above
- if (!data || order === '--') {
- return data || [];
- }
- if (!dynamicData && order === me.currentOrder) {
- return me.sortedData;
- }
- ctx.filterSpec = params.filter && Ext.decode(params.filter);
- ctx.groupSpec = params.group && Ext.decode(params.group);
- fields = params.sort;
- if (params.dir) {
- fields = [
- {
- direction: params.dir,
- property: fields
- }
- ];
- } else if (params.sort) {
- fields = Ext.decode(params.sort);
- } else {
- fields = null;
- }
- if (ctx.filterSpec) {
- filters = new Ext.util.FilterCollection();
- filters.add(this.processFilters(ctx.filterSpec));
- data = Ext.Array.filter(data, filters.getFilterFn());
- }
- sortFn = makeSortFns((ctx.sortSpec = fields));
- if (ctx.groupSpec) {
- sortFn = makeSortFns([
- ctx.groupSpec
- ], sortFn);
- }
- // If a straight Ajax request, data may not be an array.
- // If an Array, preserve 'physical' order of raw data...
- data = Ext.isArray(data) ? data.slice(0) : data;
- if (sortFn) {
- Ext.Array.sort(data, sortFn);
- }
- me.sortedData = data;
- me.currentOrder = order;
- return data;
- },
- processFilters: Ext.identityFn,
- getPage: function(ctx, data) {
- var ret = data,
- length = data.length,
- start = ctx.params.start || 0,
- end = ctx.params.limit ? Math.min(length, start + ctx.params.limit) : length;
- if (start || end < length) {
- ret = ret.slice(start, end);
- }
- return ret;
- },
- getGroupSummary: function(groupField, rows, ctx) {
- return rows[0];
- },
- getSummary: function(ctx, data, page) {
- var me = this,
- groupField = ctx.groupSpec.property,
- accum,
- todo = {},
- summary = [],
- fieldValue, lastFieldValue;
- Ext.each(page, function(rec) {
- fieldValue = rec[groupField];
- todo[fieldValue] = true;
- });
- function flush() {
- if (accum) {
- summary.push(me.getGroupSummary(groupField, accum, ctx));
- accum = null;
- }
- }
- // data is ordered primarily by the groupField, so one pass can pick up all
- // the summaries one at a time.
- Ext.each(data, function(rec) {
- fieldValue = rec[groupField];
- if (lastFieldValue !== fieldValue) {
- flush();
- lastFieldValue = fieldValue;
- }
- if (!todo[fieldValue]) {
- // if we have even 1 summary, we have summarized all that we need
- // (again because data and page are ordered by groupField)
- return !summary.length;
- }
- if (accum) {
- accum.push(rec);
- } else {
- accum = [
- rec
- ];
- }
- return true;
- });
- flush();
- // make sure that last pesky summary goes...
- return summary;
- }
- };
- }());
- /**
- * JSON Simlet.
- */
- Ext.define('Ext.ux.ajax.JsonSimlet', {
- extend: 'Ext.ux.ajax.DataSimlet',
- alias: 'simlet.json',
- doGet: function(ctx) {
- var me = this,
- data = me.getData(ctx),
- page = me.getPage(ctx, data),
- reader = ctx.xhr.options.proxy && ctx.xhr.options.proxy.getReader(),
- root = reader && reader.getRootProperty(),
- ret = me.callParent(arguments),
- // pick up status/statusText
- response = {};
- if (root && Ext.isArray(page)) {
- response[root] = page;
- response[reader.getTotalProperty()] = data.length;
- } else {
- response = page;
- }
- if (ctx.groupSpec) {
- response.summaryData = me.getSummary(ctx, data, page);
- }
- ret.responseText = Ext.encode(response);
- return ret;
- },
- doPost: function(ctx) {
- return this.doGet(ctx);
- }
- });
- /**
- * Pivot Simlet does remote pivot calculations.
- * Filtering the pivot results doesn't work.
- */
- Ext.define('Ext.ux.ajax.PivotSimlet', {
- extend: 'Ext.ux.ajax.JsonSimlet',
- alias: 'simlet.pivot',
- lastPost: null,
- // last Ajax params sent to this simlet
- lastResponse: null,
- // last JSON response produced by this simlet
- keysSeparator: '',
- grandTotalKey: '',
- doPost: function(ctx) {
- var me = this,
- ret = me.callParent(arguments);
- // pick up status/statusText
- me.lastResponse = me.processData(me.getData(ctx), Ext.decode(ctx.xhr.body));
- ret.responseText = Ext.encode(me.lastResponse);
- return ret;
- },
- processData: function(data, params) {
- var me = this,
- len = data.length,
- response = {
- success: true,
- leftAxis: [],
- topAxis: [],
- results: []
- },
- leftAxis = new Ext.util.MixedCollection(),
- topAxis = new Ext.util.MixedCollection(),
- results = new Ext.util.MixedCollection(),
- i, j, k, leftKeys, topKeys, item, agg;
- me.lastPost = params;
- me.keysSeparator = params.keysSeparator;
- me.grandTotalKey = params.grandTotalKey;
- for (i = 0; i < len; i++) {
- leftKeys = me.extractValues(data[i], params.leftAxis, leftAxis);
- topKeys = me.extractValues(data[i], params.topAxis, topAxis);
- // add record to grand totals
- me.addResult(data[i], me.grandTotalKey, me.grandTotalKey, results);
- for (j = 0; j < leftKeys.length; j++) {
- // add record to col grand totals
- me.addResult(data[i], leftKeys[j], me.grandTotalKey, results);
- // add record to left/top keys pair
- for (k = 0; k < topKeys.length; k++) {
- me.addResult(data[i], leftKeys[j], topKeys[k], results);
- }
- }
- // add record to row grand totals
- for (j = 0; j < topKeys.length; j++) {
- me.addResult(data[i], me.grandTotalKey, topKeys[j], results);
- }
- }
- // extract items from their left/top collections and build the json response
- response.leftAxis = leftAxis.getRange();
- response.topAxis = topAxis.getRange();
- len = results.getCount();
- for (i = 0; i < len; i++) {
- item = results.getAt(i);
- item.values = {};
- for (j = 0; j < params.aggregate.length; j++) {
- agg = params.aggregate[j];
- item.values[agg.id] = me[agg.aggregator](item.records, agg.dataIndex, item.leftKey, item.topKey);
- }
- delete (item.records);
- response.results.push(item);
- }
- leftAxis.clear();
- topAxis.clear();
- results.clear();
- return response;
- },
- getKey: function(value) {
- var me = this;
- me.keysMap = me.keysMap || {};
- if (!Ext.isDefined(me.keysMap[value])) {
- me.keysMap[value] = Ext.id();
- }
- return me.keysMap[value];
- },
- extractValues: function(record, dimensions, col) {
- var len = dimensions.length,
- keys = [],
- j, key, item, dim;
- key = '';
- for (j = 0; j < len; j++) {
- dim = dimensions[j];
- key += (j > 0 ? this.keysSeparator : '') + this.getKey(record[dim.dataIndex]);
- item = col.getByKey(key);
- if (!item) {
- item = col.add(key, {
- key: key,
- value: record[dim.dataIndex],
- dimensionId: dim.id
- });
- }
- keys.push(key);
- }
- return keys;
- },
- addResult: function(record, leftKey, topKey, results) {
- var item = results.getByKey(leftKey + '/' + topKey);
- if (!item) {
- item = results.add(leftKey + '/' + topKey, {
- leftKey: leftKey,
- topKey: topKey,
- records: []
- });
- }
- item.records.push(record);
- },
- sum: function(records, measure, rowGroupKey, colGroupKey) {
- var length = records.length,
- total = 0,
- i;
- for (i = 0; i < length; i++) {
- total += Ext.Number.from(records[i][measure], 0);
- }
- return total;
- },
- avg: function(records, measure, rowGroupKey, colGroupKey) {
- var length = records.length,
- total = 0,
- i;
- for (i = 0; i < length; i++) {
- total += Ext.Number.from(records[i][measure], 0);
- }
- return length > 0 ? (total / length) : 0;
- },
- min: function(records, measure, rowGroupKey, colGroupKey) {
- var data = [],
- length = records.length,
- i, v;
- for (i = 0; i < length; i++) {
- data.push(records[i][measure]);
- }
- v = Ext.Array.min(data);
- return v;
- },
- max: function(records, measure, rowGroupKey, colGroupKey) {
- var data = [],
- length = records.length,
- i, v;
- for (i = 0; i < length; i++) {
- data.push(records[i][measure]);
- }
- v = Ext.Array.max(data);
- return v;
- },
- count: function(records, measure, rowGroupKey, colGroupKey) {
- return records.length;
- },
- variance: function(records, measure, rowGroupKey, colGroupKey) {
- var me = Ext.pivot.Aggregators,
- length = records.length,
- avg = me.avg.apply(me, arguments),
- total = 0,
- i;
- if (avg > 0) {
- for (i = 0; i < length; i++) {
- total += Math.pow(Ext.Number.from(records[i][measure], 0) - avg, 2);
- }
- }
- return (total > 0 && length > 1) ? (total / (length - 1)) : 0;
- },
- varianceP: function(records, measure, rowGroupKey, colGroupKey) {
- var me = Ext.pivot.Aggregators,
- length = records.length,
- avg = me.avg.apply(me, arguments),
- total = 0,
- i;
- if (avg > 0) {
- for (i = 0; i < length; i++) {
- total += Math.pow(Ext.Number.from(records[i][measure], 0) - avg, 2);
- }
- }
- return (total > 0 && length > 0) ? (total / length) : 0;
- },
- stdDev: function(records, measure, rowGroupKey, colGroupKey) {
- var me = Ext.pivot.Aggregators,
- v = me.variance.apply(me, arguments);
- return v > 0 ? Math.sqrt(v) : 0;
- },
- stdDevP: function(records, measure, rowGroupKey, colGroupKey) {
- var me = Ext.pivot.Aggregators,
- v = me.varianceP.apply(me, arguments);
- return v > 0 ? Math.sqrt(v) : 0;
- }
- });
- /**
- * Simulates an XMLHttpRequest object's methods and properties but is backed by a
- * {@link Ext.ux.ajax.Simlet} instance that provides the data.
- */
- Ext.define('Ext.ux.ajax.SimXhr', {
- readyState: 0,
- mgr: null,
- simlet: null,
- constructor: function(config) {
- var me = this;
- Ext.apply(me, config);
- me.requestHeaders = {};
- },
- abort: function() {
- var me = this;
- if (me.timer) {
- Ext.undefer(me.timer);
- me.timer = null;
- }
- me.aborted = true;
- },
- getAllResponseHeaders: function() {
- var headers = [];
- if (Ext.isObject(this.responseHeaders)) {
- Ext.Object.each(this.responseHeaders, function(name, value) {
- headers.push(name + ': ' + value);
- });
- }
- return headers.join('\r\n');
- },
- getResponseHeader: function(header) {
- var headers = this.responseHeaders;
- return (headers && headers[header]) || null;
- },
- open: function(method, url, async, user, password) {
- var me = this;
- me.method = method;
- me.url = url;
- me.async = async !== false;
- me.user = user;
- me.password = password;
- me.setReadyState(1);
- },
- overrideMimeType: function(mimeType) {
- this.mimeType = mimeType;
- },
- schedule: function() {
- var me = this,
- delay = me.simlet.delay || me.mgr.delay;
- if (delay) {
- me.timer = Ext.defer(function() {
- me.onTick();
- }, delay);
- } else {
- me.onTick();
- }
- },
- send: function(body) {
- var me = this;
- me.body = body;
- if (me.async) {
- me.schedule();
- } else {
- me.onComplete();
- }
- },
- setReadyState: function(state) {
- var me = this;
- if (me.readyState !== state) {
- me.readyState = state;
- me.onreadystatechange();
- }
- },
- setRequestHeader: function(header, value) {
- this.requestHeaders[header] = value;
- },
- // handlers
- onreadystatechange: Ext.emptyFn,
- onComplete: function() {
- var me = this,
- callback, text;
- me.readyState = 4;
- Ext.apply(me, me.simlet.exec(me));
- callback = me.jsonpCallback;
- if (callback) {
- text = callback + '(' + me.responseText + ')';
- eval(text);
- }
- },
- onTick: function() {
- var me = this;
- me.timer = null;
- me.onComplete();
- if (me.onreadystatechange) {
- me.onreadystatechange();
- }
- }
- });
- /**
- * This singleton manages simulated Ajax responses. This allows application logic to be
- * written unaware that its Ajax calls are being handled by simulations ("simlets"). This
- * is currently done by hooking {@link Ext.data.Connection} methods, so all users of that
- * class (and {@link Ext.Ajax} since it is a derived class) qualify for simulation.
- *
- * The requires hooks are inserted when either the {@link #init} method is called or the
- * first {@link Ext.ux.ajax.Simlet} is registered. For example:
- *
- * Ext.onReady(function () {
- * initAjaxSim();
- *
- * // normal stuff
- * });
- *
- * function initAjaxSim () {
- * Ext.ux.ajax.SimManager.init({
- * delay: 300
- * }).register({
- * '/app/data/url': {
- * type: 'json', // use JsonSimlet (type is like xtype for components)
- * data: [
- * { foo: 42, bar: 'abc' },
- * ...
- * ]
- * }
- * });
- * }
- *
- * As many URL's as desired can be registered and associated with a {@link Ext.ux.ajax.Simlet}.
- * To make non-simulated Ajax requests once this singleton is initialized, add a `nosim:true` option
- * to the Ajax options:
- *
- * Ext.Ajax.request({
- * url: 'page.php',
- * nosim: true, // ignored by normal Ajax request
- * params: {
- * id: 1
- * },
- * success: function(response){
- * var text = response.responseText;
- * // process server response here
- * }
- * });
- */
- Ext.define('Ext.ux.ajax.SimManager', {
- singleton: true,
- requires: [
- 'Ext.data.Connection',
- 'Ext.ux.ajax.SimXhr',
- 'Ext.ux.ajax.Simlet',
- 'Ext.ux.ajax.JsonSimlet'
- ],
- /**
- * @cfg {Ext.ux.ajax.Simlet} defaultSimlet
- * The {@link Ext.ux.ajax.Simlet} instance to use for non-matching URL's. By default, this will
- * return 404. Set this to null to use real Ajax calls for non-matching URL's.
- */
- /**
- * @cfg {String} defaultType
- * The default `type` to apply to generic {@link Ext.ux.ajax.Simlet} configuration objects. The
- * default is 'basic'.
- */
- defaultType: 'basic',
- /**
- * @cfg {Number} delay
- * The number of milliseconds to delay before delivering a response to an async request.
- */
- delay: 150,
- /**
- * @property {Boolean} ready
- * True once this singleton has initialized and applied its Ajax hooks.
- * @private
- */
- ready: false,
- constructor: function() {
- this.simlets = [];
- },
- getSimlet: function(url) {
- // Strip down to base URL (no query parameters or hash):
- var me = this,
- index = url.indexOf('?'),
- simlets = me.simlets,
- len = simlets.length,
- i, simlet, simUrl, match;
- if (index < 0) {
- index = url.indexOf('#');
- }
- if (index > 0) {
- url = url.substring(0, index);
- }
- for (i = 0; i < len; ++i) {
- simlet = simlets[i];
- simUrl = simlet.url;
- if (simUrl instanceof RegExp) {
- match = simUrl.test(url);
- } else {
- match = simUrl === url;
- }
- if (match) {
- return simlet;
- }
- }
- return me.defaultSimlet;
- },
- getXhr: function(method, url, options, async) {
- var simlet = this.getSimlet(url);
- if (simlet) {
- return simlet.openRequest(method, url, options, async);
- }
- return null;
- },
- /**
- * Initializes this singleton and applies configuration options.
- * @param {Object} config An optional object with configuration properties to apply.
- * @return {Ext.ux.ajax.SimManager} this
- */
- init: function(config) {
- var me = this;
- Ext.apply(me, config);
- if (!me.ready) {
- me.ready = true;
- if (!('defaultSimlet' in me)) {
- me.defaultSimlet = new Ext.ux.ajax.Simlet({
- status: 404,
- statusText: 'Not Found'
- });
- }
- me._openRequest = Ext.data.Connection.prototype.openRequest;
- Ext.data.request.Ajax.override({
- openRequest: function(options, requestOptions, async) {
- var xhr = !options.nosim && me.getXhr(requestOptions.method, requestOptions.url, options, async);
- if (!xhr) {
- xhr = this.callParent(arguments);
- }
- return xhr;
- }
- });
- if (Ext.data.JsonP) {
- Ext.data.JsonP.self.override({
- createScript: function(url, params, options) {
- var fullUrl = Ext.urlAppend(url, Ext.Object.toQueryString(params)),
- script = !options.nosim && me.getXhr('GET', fullUrl, options, true);
- if (!script) {
- script = this.callParent(arguments);
- }
- return script;
- },
- loadScript: function(request) {
- var script = request.script;
- if (script.simlet) {
- script.jsonpCallback = request.params[request.callbackKey];
- script.send(null);
- // Ext.data.JsonP will attempt dom removal of a script tag,
- // so emulate its presence
- request.script = document.createElement('script');
- } else {
- this.callParent(arguments);
- }
- }
- });
- }
- }
- return me;
- },
- openRequest: function(method, url, async) {
- var opt = {
- method: method,
- url: url
- };
- return this._openRequest.call(Ext.data.Connection.prototype, {}, opt, async);
- },
- /**
- * Registeres one or more {@link Ext.ux.ajax.Simlet} instances.
- * @param {Array/Object} simlet Either a {@link Ext.ux.ajax.Simlet} instance or config, an Array
- * of such elements or an Object keyed by URL with values that are {@link Ext.ux.ajax.Simlet}
- * instances or configs.
- */
- register: function(simlet) {
- var me = this;
- me.init();
- function reg(one) {
- var simlet = one;
- if (!simlet.isSimlet) {
- simlet = Ext.create('simlet.' + (simlet.type || simlet.stype || me.defaultType), one);
- }
- me.simlets.push(simlet);
- simlet.manager = me;
- }
- if (Ext.isArray(simlet)) {
- Ext.each(simlet, reg);
- } else if (simlet.isSimlet || simlet.url) {
- reg(simlet);
- } else {
- Ext.Object.each(simlet, function(url, s) {
- s.url = url;
- reg(s);
- });
- }
- return me;
- }
- });
- /**
- * This class simulates XML-based requests.
- */
- Ext.define('Ext.ux.ajax.XmlSimlet', {
- extend: 'Ext.ux.ajax.DataSimlet',
- alias: 'simlet.xml',
- /* eslint-disable indent */
- /**
- * This template is used to populate the XML response. The configuration of the Reader
- * is available so that its `root` and `record` properties can be used as well as the
- * `fields` of the associated `model`. But beyond that, the way these pieces are put
- * together in the document requires the flexibility of a template.
- */
- xmlTpl: [
- '<{root}>\n',
- '<tpl for="data">',
- ' <{parent.record}>\n',
- '<tpl for="parent.fields">',
- ' <{name}>{[parent[values.name]]}</{name}>\n',
- '</tpl>',
- ' </{parent.record}>\n',
- '</tpl>',
- '</{root}>'
- ],
- /* eslint-enable indent */
- doGet: function(ctx) {
- var me = this,
- data = me.getData(ctx),
- page = me.getPage(ctx, data),
- proxy = ctx.xhr.options.operation.getProxy(),
- reader = proxy && proxy.getReader(),
- model = reader && reader.getModel(),
- ret = me.callParent(arguments),
- // pick up status/statusText
- response = {
- data: page,
- reader: reader,
- fields: model && model.fields,
- root: reader && reader.getRootProperty(),
- record: reader && reader.record
- },
- tpl, xml, doc;
- if (ctx.groupSpec) {
- response.summaryData = me.getSummary(ctx, data, page);
- }
- // If a straight Ajax request there won't be an xmlTpl.
- if (me.xmlTpl) {
- tpl = Ext.XTemplate.getTpl(me, 'xmlTpl');
- xml = tpl.apply(response);
- } else {
- xml = data;
- }
- if (typeof DOMParser !== 'undefined') {
- doc = (new DOMParser()).parseFromString(xml, "text/xml");
- } else {
- /* global ActiveXObject */
- // IE doesn't have DOMParser, but fortunately, there is an ActiveX for XML
- doc = new ActiveXObject("Microsoft.XMLDOM");
- doc.async = false;
- doc.loadXML(xml);
- }
- ret.responseText = xml;
- ret.responseXML = doc;
- return ret;
- },
- fixTree: function() {
- var buffer = [];
- this.callParent(arguments);
- this.buildTreeXml(this.data, buffer);
- this.data = buffer.join('');
- },
- buildTreeXml: function(nodes, buffer) {
- var rootProperty = this.rootProperty,
- recordProperty = this.recordProperty;
- buffer.push('<', rootProperty, '>');
- Ext.Array.forEach(nodes, function(node) {
- var key;
- buffer.push('<', recordProperty, '>');
- for (key in node) {
- if (key === 'children') {
- this.buildTreeXml(node.children, buffer);
- } else {
- buffer.push('<', key, '>', node[key], '</', key, '>');
- }
- }
- buffer.push('</', recordProperty, '>');
- });
- buffer.push('</', rootProperty, '>');
- }
- });
- /**
- * This is the base class for {@link Ext.ux.event.Recorder} and {@link Ext.ux.event.Player}.
- */
- Ext.define('Ext.ux.event.Driver', {
- extend: 'Ext.util.Observable',
- active: null,
- specialKeysByName: {
- PGUP: 33,
- PGDN: 34,
- END: 35,
- HOME: 36,
- LEFT: 37,
- UP: 38,
- RIGHT: 39,
- DOWN: 40
- },
- specialKeysByCode: {},
- /**
- * @event start
- * Fires when this object is started.
- * @param {Ext.ux.event.Driver} this
- */
- /**
- * @event stop
- * Fires when this object is stopped.
- * @param {Ext.ux.event.Driver} this
- */
- getTextSelection: function(el) {
- // See https://code.google.com/p/rangyinputs/source/browse/trunk/rangyinputs_jquery.js
- var doc = el.ownerDocument,
- range, range2, start, end;
- if (typeof el.selectionStart === "number") {
- start = el.selectionStart;
- end = el.selectionEnd;
- } else if (doc.selection) {
- range = doc.selection.createRange();
- range2 = el.createTextRange();
- range2.setEndPoint('EndToStart', range);
- start = range2.text.length;
- end = start + range.text.length;
- }
- return [
- start,
- end
- ];
- },
- getTime: function() {
- return new Date().getTime();
- },
- /**
- * Returns the number of milliseconds since start was called.
- */
- getTimestamp: function() {
- var d = this.getTime();
- return d - this.startTime;
- },
- onStart: function() {},
- onStop: function() {},
- /**
- * Starts this object. If this object is already started, nothing happens.
- */
- start: function() {
- var me = this;
- if (!me.active) {
- me.active = new Date();
- me.startTime = me.getTime();
- me.onStart();
- me.fireEvent('start', me);
- }
- },
- /**
- * Stops this object. If this object is not started, nothing happens.
- */
- stop: function() {
- var me = this;
- if (me.active) {
- me.active = null;
- me.onStop();
- me.fireEvent('stop', me);
- }
- }
- }, function() {
- var proto = this.prototype;
- Ext.Object.each(proto.specialKeysByName, function(name, value) {
- proto.specialKeysByCode[value] = name;
- });
- });
- /**
- * Event maker.
- */
- Ext.define('Ext.ux.event.Maker', {
- eventQueue: [],
- startAfter: 500,
- timerIncrement: 500,
- currentTiming: 0,
- constructor: function(config) {
- var me = this;
- me.currentTiming = me.startAfter;
- if (!Ext.isArray(config)) {
- config = [
- config
- ];
- }
- Ext.Array.each(config, function(item) {
- item.el = item.el || 'el';
- Ext.Array.each(Ext.ComponentQuery.query(item.cmpQuery), function(cmp) {
- var event = {},
- x, y, el;
- if (!item.domQuery) {
- el = cmp[item.el];
- } else {
- el = cmp.el.down(item.domQuery);
- }
- event.target = '#' + el.dom.id;
- event.type = item.type;
- event.button = config.button || 0;
- x = el.getX() + (el.getWidth() / 2);
- y = el.getY() + (el.getHeight() / 2);
- event.xy = [
- x,
- y
- ];
- event.ts = me.currentTiming;
- me.currentTiming += me.timerIncrement;
- me.eventQueue.push(event);
- });
- if (item.screenshot) {
- me.eventQueue[me.eventQueue.length - 1].screenshot = true;
- }
- });
- return me.eventQueue;
- }
- });
- /**
- * @extends Ext.ux.event.Driver
- * This class manages the playback of an array of "event descriptors". For details on the
- * contents of an "event descriptor", see {@link Ext.ux.event.Recorder}. The events recorded by the
- * {@link Ext.ux.event.Recorder} class are designed to serve as input for this class.
- *
- * The simplest use of this class is to instantiate it with an {@link #eventQueue} and call
- * {@link #method-start}. Like so:
- *
- * var player = Ext.create('Ext.ux.event.Player', {
- * eventQueue: [ ... ],
- * speed: 2, // play at 2x speed
- * listeners: {
- * stop: function() {
- * player = null; // all done
- * }
- * }
- * });
- *
- * player.start();
- *
- * A more complex use would be to incorporate keyframe generation after playing certain
- * events.
- *
- * var player = Ext.create('Ext.ux.event.Player', {
- * eventQueue: [ ... ],
- * keyFrameEvents: {
- * click: true
- * },
- * listeners: {
- * stop: function() {
- * // play has completed... probably time for another keyframe...
- * player = null;
- * },
- * keyframe: onKeyFrame
- * }
- * });
- *
- * player.start();
- *
- * If a keyframe can be handled immediately (synchronously), the listener would be:
- *
- * function onKeyFrame () {
- * handleKeyFrame();
- * }
- *
- * If the keyframe event is always handled asynchronously, then the event listener is only
- * a bit more:
- *
- * function onKeyFrame (p, eventDescriptor) {
- * eventDescriptor.defer(); // pause event playback...
- *
- * handleKeyFrame(function() {
- * eventDescriptor.finish(); // ...resume event playback
- * });
- * }
- *
- * Finally, if the keyframe could be either handled synchronously or asynchronously (perhaps
- * differently by browser), a slightly more complex listener is required.
- *
- * function onKeyFrame (p, eventDescriptor) {
- * var async;
- *
- * handleKeyFrame(function() {
- * // either this callback is being called immediately by handleKeyFrame (in
- * // which case async is undefined) or it is being called later (in which case
- * // async will be true).
- *
- * if (async) {
- * eventDescriptor.finish();
- * }
- * else {
- * async = false;
- * }
- * });
- *
- * // either the callback was called (and async is now false) or it was not
- * // called (and async remains undefined).
- *
- * if (async !== false) {
- * eventDescriptor.defer();
- * async = true; // let the callback know that we have gone async
- * }
- * }
- */
- Ext.define('Ext.ux.event.Player', function(Player) {
- /* eslint-disable indent, vars-on-top, one-var */
- var defaults = {},
- mouseEvents = {},
- keyEvents = {},
- doc,
- // HTML events supported
- uiEvents = {},
- // events that bubble by default
- bubbleEvents = {
- // scroll: 1,
- resize: 1,
- reset: 1,
- submit: 1,
- change: 1,
- select: 1,
- error: 1,
- abort: 1
- };
- Ext.each([
- 'click',
- 'dblclick',
- 'mouseover',
- 'mouseout',
- 'mousedown',
- 'mouseup',
- 'mousemove'
- ], function(type) {
- bubbleEvents[type] = defaults[type] = mouseEvents[type] = {
- bubbles: true,
- cancelable: (type !== "mousemove"),
- // mousemove cannot be cancelled
- detail: 1,
- screenX: 0,
- screenY: 0,
- clientX: 0,
- clientY: 0,
- ctrlKey: false,
- altKey: false,
- shiftKey: false,
- metaKey: false,
- button: 0
- };
- });
- Ext.each([
- 'keydown',
- 'keyup',
- 'keypress'
- ], function(type) {
- bubbleEvents[type] = defaults[type] = keyEvents[type] = {
- bubbles: true,
- cancelable: true,
- ctrlKey: false,
- altKey: false,
- shiftKey: false,
- metaKey: false,
- keyCode: 0,
- charCode: 0
- };
- });
- Ext.each([
- 'blur',
- 'change',
- 'focus',
- 'resize',
- 'scroll',
- 'select'
- ], function(type) {
- defaults[type] = uiEvents[type] = {
- bubbles: (type in bubbleEvents),
- cancelable: false,
- detail: 1
- };
- });
- var inputSpecialKeys = {
- 8: function(target, start, end) {
- // backspace: 8,
- if (start < end) {
- target.value = target.value.substring(0, start) + target.value.substring(end);
- } else if (start > 0) {
- target.value = target.value.substring(0, --start) + target.value.substring(end);
- }
- this.setTextSelection(target, start, start);
- },
- 46: function(target, start, end) {
- // delete: 46
- if (start < end) {
- target.value = target.value.substring(0, start) + target.value.substring(end);
- } else if (start < target.value.length - 1) {
- target.value = target.value.substring(0, start) + target.value.substring(start + 1);
- }
- this.setTextSelection(target, start, start);
- }
- };
- return {
- extend: 'Ext.ux.event.Driver',
- /**
- * @cfg {Array} eventQueue The event queue to playback. This must be provided before
- * the {@link #method-start} method is called.
- */
- /**
- * @cfg {Object} keyFrameEvents An object that describes the events that should generate
- * keyframe events. For example, `{ click: true }` would generate keyframe events after
- * each `click` event.
- */
- keyFrameEvents: {
- click: true
- },
- /**
- * @cfg {Boolean} pauseForAnimations True to pause event playback during animations, false
- * to ignore animations. Default is true.
- */
- pauseForAnimations: true,
- /**
- * @cfg {Number} speed The playback speed multiplier. Default is 1.0 (to playback at the
- * recorded speed). A value of 2 would playback at 2x speed.
- */
- speed: 1,
- stallTime: 0,
- _inputSpecialKeys: {
- INPUT: inputSpecialKeys,
- TEXTAREA: Ext.apply({}, // 13: function(target, start, end) { // enter: 8,
- // TODO ?
- // }
- inputSpecialKeys)
- },
- tagPathRegEx: /(\w+)(?:\[(\d+)\])?/,
- /**
- * @event beforeplay
- * Fires before an event is played.
- * @param {Ext.ux.event.Player} this
- * @param {Object} eventDescriptor The event descriptor about to be played.
- */
- /**
- * @event keyframe
- * Fires when this player reaches a keyframe. Typically, this is after events
- * like `click` are injected and any resulting animations have been completed.
- * @param {Ext.ux.event.Player} this
- * @param {Object} eventDescriptor The keyframe event descriptor.
- */
- constructor: function(config) {
- var me = this;
- me.callParent(arguments);
- me.timerFn = function() {
- me.onTick();
- };
- me.attachTo = me.attachTo || window;
- doc = me.attachTo.document;
- },
- /**
- * Returns the element given is XPath-like description.
- * @param {String} xpath The XPath-like description of the element.
- * @return {HTMLElement}
- */
- getElementFromXPath: function(xpath) {
- var me = this,
- parts = xpath.split('/'),
- regex = me.tagPathRegEx,
- i, n, m, count, tag, child,
- el = me.attachTo.document;
- el = (parts[0] === '~') ? el.body : el.getElementById(parts[0].substring(1));
- // remove '#'
- for (i = 1 , n = parts.length; el && i < n; ++i) {
- m = regex.exec(parts[i]);
- count = m[2] ? parseInt(m[2], 10) : 1;
- tag = m[1].toUpperCase();
- for (child = el.firstChild; child; child = child.nextSibling) {
- if (child.tagName === tag) {
- if (count === 1) {
- break;
- }
- --count;
- }
- }
- el = child;
- }
- return el;
- },
- // Moving across a line break only counts as moving one character in a TextRange, whereas
- // a line break in the textarea value is two characters. This function corrects for that
- // by converting a text offset into a range character offset by subtracting one character
- // for every line break in the textarea prior to the offset
- offsetToRangeCharacterMove: function(el, offset) {
- return offset - (el.value.slice(0, offset).split("\r\n").length - 1);
- },
- setTextSelection: function(el, startOffset, endOffset) {
- var range, startCharMove;
- // See https://code.google.com/p/rangyinputs/source/browse/trunk/rangyinputs_jquery.js
- if (startOffset < 0) {
- startOffset += el.value.length;
- }
- if (endOffset == null) {
- endOffset = startOffset;
- }
- if (endOffset < 0) {
- endOffset += el.value.length;
- }
- if (typeof el.selectionStart === "number") {
- el.selectionStart = startOffset;
- el.selectionEnd = endOffset;
- } else {
- range = el.createTextRange();
- startCharMove = this.offsetToRangeCharacterMove(el, startOffset);
- range.collapse(true);
- if (startOffset === endOffset) {
- range.move("character", startCharMove);
- } else {
- range.moveEnd("character", this.offsetToRangeCharacterMove(el, endOffset));
- range.moveStart("character", startCharMove);
- }
- range.select();
- }
- },
- getTimeIndex: function() {
- var t = this.getTimestamp() - this.stallTime;
- return t * this.speed;
- },
- makeToken: function(eventDescriptor, signal) {
- var me = this,
- t0;
- eventDescriptor[signal] = true;
- eventDescriptor.defer = function() {
- eventDescriptor[signal] = false;
- t0 = me.getTime();
- };
- eventDescriptor.finish = function() {
- eventDescriptor[signal] = true;
- me.stallTime += me.getTime() - t0;
- me.schedule();
- };
- },
- /**
- * This method is called after an event has been played to prepare for the next event.
- * @param {Object} eventDescriptor The descriptor of the event just played.
- */
- nextEvent: function(eventDescriptor) {
- var me = this,
- index = ++me.queueIndex;
- // keyframe events are inserted after a keyFrameEvent is played.
- if (me.keyFrameEvents[eventDescriptor.type]) {
- Ext.Array.insert(me.eventQueue, index, [
- {
- keyframe: true,
- ts: eventDescriptor.ts
- }
- ]);
- }
- },
- /**
- * This method returns the event descriptor at the front of the queue. This does not
- * dequeue the event. Repeated calls return the same object (until {@link #nextEvent}
- * is called).
- */
- peekEvent: function() {
- return this.eventQueue[this.queueIndex] || null;
- },
- /**
- * Replaces an event in the queue with an array of events. This is often used to roll
- * up a multi-step pseudo-event and expand it just-in-time to be played. The process
- * for doing this in a derived class would be this:
- *
- * Ext.define('My.Player', {
- * extend: 'Ext.ux.event.Player',
- *
- * peekEvent: function() {
- * var event = this.callParent();
- *
- * if (event.multiStepSpecial) {
- * this.replaceEvent(null, [
- * ... expand to actual events
- * ]);
- *
- * event = this.callParent(); // get the new next event
- * }
- *
- * return event;
- * }
- * });
- *
- * This method ensures that the `beforeplay` hook (if any) from the replaced event is
- * placed on the first new event and the `afterplay` hook (if any) is placed on the
- * last new event.
- *
- * @param {Number} index The queue index to replace. Pass `null` to replace the event
- * at the current `queueIndex`.
- * @param {Event[]} events The array of events with which to replace the specified
- * event.
- */
- replaceEvent: function(index, events) {
- for (var t,
- i = 0,
- n = events.length; i < n; ++i) {
- if (i) {
- t = events[i - 1];
- delete t.afterplay;
- delete t.screenshot;
- delete events[i].beforeplay;
- }
- }
- Ext.Array.replace(this.eventQueue, (index == null) ? this.queueIndex : index, 1, events);
- },
- /**
- * This method dequeues and injects events until it has arrived at the time index. If
- * no events are ready (based on the time index), this method does nothing.
- * @return {Boolean} True if there is more to do; false if not (at least for now).
- */
- processEvents: function() {
- var me = this,
- animations = me.pauseForAnimations && me.attachTo.Ext.fx.Manager.items,
- eventDescriptor;
- while ((eventDescriptor = me.peekEvent()) !== null) {
- if (animations && animations.getCount()) {
- return true;
- }
- if (eventDescriptor.keyframe) {
- if (!me.processKeyFrame(eventDescriptor)) {
- return false;
- }
- me.nextEvent(eventDescriptor);
- } else if (eventDescriptor.ts <= me.getTimeIndex() && me.fireEvent('beforeplay', me, eventDescriptor) !== false && me.playEvent(eventDescriptor)) {
- me.nextEvent(eventDescriptor);
- } else {
- return true;
- }
- }
- me.stop();
- return false;
- },
- /**
- * This method is called when a keyframe is reached. This will fire the keyframe event.
- * If the keyframe has been handled, true is returned. Otherwise, false is returned.
- * @param {Object} eventDescriptor The event descriptor of the keyframe.
- * @return {Boolean} True if the keyframe was handled, false if not.
- */
- processKeyFrame: function(eventDescriptor) {
- var me = this;
- // only fire keyframe event (and setup the eventDescriptor) once...
- if (!eventDescriptor.defer) {
- me.makeToken(eventDescriptor, 'done');
- me.fireEvent('keyframe', me, eventDescriptor);
- }
- return eventDescriptor.done;
- },
- /**
- * Called to inject the given event on the specified target.
- * @param {HTMLElement} target The target of the event.
- * @param {Object} event The event to inject. The properties of this object should be
- * those of standard DOM events but vary based on the `type` property. For details on
- * event types and their properties, see the class documentation.
- */
- injectEvent: function(target, event) {
- var me = this,
- type = event.type,
- options = Ext.apply({}, event, defaults[type]),
- handler;
- if (type === 'type') {
- handler = me._inputSpecialKeys[target.tagName];
- if (handler) {
- return me.injectTypeInputEvent(target, event, handler);
- }
- return me.injectTypeEvent(target, event);
- }
- if (type === 'focus' && target.focus) {
- target.focus();
- return true;
- }
- if (type === 'blur' && target.blur) {
- target.blur();
- return true;
- }
- if (type === 'scroll') {
- target.scrollLeft = event.pos[0];
- target.scrollTop = event.pos[1];
- return true;
- }
- if (type === 'mduclick') {
- return me.injectEvent(target, Ext.applyIf({
- type: 'mousedown'
- }, event)) && me.injectEvent(target, Ext.applyIf({
- type: 'mouseup'
- }, event)) && me.injectEvent(target, Ext.applyIf({
- type: 'click'
- }, event));
- }
- if (mouseEvents[type]) {
- return Player.injectMouseEvent(target, options, me.attachTo);
- }
- if (keyEvents[type]) {
- return Player.injectKeyEvent(target, options, me.attachTo);
- }
- if (uiEvents[type]) {
- return Player.injectUIEvent(target, type, options.bubbles, options.cancelable, options.view || me.attachTo, options.detail);
- }
- return false;
- },
- injectTypeEvent: function(target, event) {
- var me = this,
- text = event.text,
- xlat = [],
- ch, chUp, i, n, upper;
- if (text) {
- delete event.text;
- upper = text.toUpperCase();
- for (i = 0 , n = text.length; i < n; ++i) {
- ch = text.charCodeAt(i);
- chUp = upper.charCodeAt(i);
- xlat.push(Ext.applyIf({
- type: 'keydown',
- charCode: chUp,
- keyCode: chUp
- }, event), Ext.applyIf({
- type: 'keypress',
- charCode: ch,
- keyCode: ch
- }, event), Ext.applyIf({
- type: 'keyup',
- charCode: chUp,
- keyCode: chUp
- }, event));
- }
- } else {
- xlat.push(Ext.applyIf({
- type: 'keydown',
- charCode: event.keyCode
- }, event), Ext.applyIf({
- type: 'keyup',
- charCode: event.keyCode
- }, event));
- }
- for (i = 0 , n = xlat.length; i < n; ++i) {
- me.injectEvent(target, xlat[i]);
- }
- return true;
- },
- injectTypeInputEvent: function(target, event, handler) {
- var me = this,
- text = event.text,
- sel, n;
- if (handler) {
- sel = me.getTextSelection(target);
- if (text) {
- n = sel[0];
- target.value = target.value.substring(0, n) + text + target.value.substring(sel[1]);
- n += text.length;
- me.setTextSelection(target, n, n);
- } else {
- if (!(handler = handler[event.keyCode])) {
- // no handler for the special key for this element
- if ('caret' in event) {
- me.setTextSelection(target, event.caret, event.caret);
- } else if (event.selection) {
- me.setTextSelection(target, event.selection[0], event.selection[1]);
- }
- return me.injectTypeEvent(target, event);
- }
- handler.call(this, target, sel[0], sel[1]);
- return true;
- }
- }
- return true;
- },
- playEvent: function(eventDescriptor) {
- var me = this,
- target = me.getElementFromXPath(eventDescriptor.target),
- event;
- if (!target) {
- // not present (yet)... wait for element present...
- // TODO - need a timeout here
- return false;
- }
- if (!me.playEventHook(eventDescriptor, 'beforeplay')) {
- return false;
- }
- if (!eventDescriptor.injected) {
- eventDescriptor.injected = true;
- event = me.translateEvent(eventDescriptor, target);
- me.injectEvent(target, event);
- }
- return me.playEventHook(eventDescriptor, 'afterplay');
- },
- playEventHook: function(eventDescriptor, hookName) {
- var me = this,
- doneName = hookName + '.done',
- firedName = hookName + '.fired',
- hook = eventDescriptor[hookName];
- if (hook && !eventDescriptor[doneName]) {
- if (!eventDescriptor[firedName]) {
- eventDescriptor[firedName] = true;
- me.makeToken(eventDescriptor, doneName);
- if (me.eventScope && Ext.isString(hook)) {
- hook = me.eventScope[hook];
- }
- if (hook) {
- hook.call(me.eventScope || me, eventDescriptor);
- }
- }
- return false;
- }
- return true;
- },
- schedule: function() {
- var me = this;
- if (!me.timer) {
- me.timer = Ext.defer(me.timerFn, 10);
- }
- },
- _translateAcross: [
- 'type',
- 'button',
- 'charCode',
- 'keyCode',
- 'caret',
- 'pos',
- 'text',
- 'selection'
- ],
- translateEvent: function(eventDescriptor, target) {
- var me = this,
- event = {},
- modKeys = eventDescriptor.modKeys || '',
- names = me._translateAcross,
- i = names.length,
- name, xy;
- while (i--) {
- name = names[i];
- if (name in eventDescriptor) {
- event[name] = eventDescriptor[name];
- }
- }
- event.altKey = modKeys.indexOf('A') > 0;
- event.ctrlKey = modKeys.indexOf('C') > 0;
- event.metaKey = modKeys.indexOf('M') > 0;
- event.shiftKey = modKeys.indexOf('S') > 0;
- if (target && 'x' in eventDescriptor) {
- xy = Ext.fly(target).getXY();
- xy[0] += eventDescriptor.x;
- xy[1] += eventDescriptor.y;
- } else if ('x' in eventDescriptor) {
- xy = [
- eventDescriptor.x,
- eventDescriptor.y
- ];
- } else if ('px' in eventDescriptor) {
- xy = [
- eventDescriptor.px,
- eventDescriptor.py
- ];
- }
- if (xy) {
- event.clientX = event.screenX = xy[0];
- event.clientY = event.screenY = xy[1];
- }
- if (eventDescriptor.key) {
- event.keyCode = me.specialKeysByName[eventDescriptor.key];
- }
- if (eventDescriptor.type === 'wheel') {
- if ('onwheel' in me.attachTo.document) {
- event.wheelX = eventDescriptor.dx;
- event.wheelY = eventDescriptor.dy;
- } else {
- event.type = 'mousewheel';
- event.wheelDeltaX = -40 * eventDescriptor.dx;
- event.wheelDeltaY = event.wheelDelta = -40 * eventDescriptor.dy;
- }
- }
- return event;
- },
- //---------------------------------
- // Driver overrides
- onStart: function() {
- var me = this;
- me.queueIndex = 0;
- me.schedule();
- },
- onStop: function() {
- var me = this;
- if (me.timer) {
- Ext.undefer(me.timer);
- me.timer = null;
- }
- },
- //---------------------------------
- onTick: function() {
- var me = this;
- me.timer = null;
- if (me.processEvents()) {
- me.schedule();
- }
- },
- statics: {
- ieButtonCodeMap: {
- 0: 1,
- 1: 4,
- 2: 2
- },
- /**
- * Injects a key event using the given event information to populate the event
- * object.
- *
- * **Note:** `keydown` causes Safari 2.x to crash.
- *
- * @param {HTMLElement} target The target of the given event.
- * @param {Object} options Object object containing all of the event injection
- * options.
- * @param {String} options.type The type of event to fire. This can be any one of
- * the following: `keyup`, `keydown` and `keypress`.
- * @param {Boolean} [options.bubbles=true] `tru` if the event can be bubbled up.
- * DOM Level 3 specifies that all key events bubble by default.
- * @param {Boolean} [options.cancelable=true] `true` if the event can be canceled
- * using `preventDefault`. DOM Level 3 specifies that all key events can be
- * cancelled.
- * @param {Boolean} [options.ctrlKey=false] `true` if one of the CTRL keys is
- * pressed while the event is firing.
- * @param {Boolean} [options.altKey=false] `true` if one of the ALT keys is
- * pressed while the event is firing.
- * @param {Boolean} [options.shiftKey=false] `true` if one of the SHIFT keys is
- * pressed while the event is firing.
- * @param {Boolean} [options.metaKey=false] `true` if one of the META keys is
- * pressed while the event is firing.
- * @param {Number} [options.keyCode=0] The code for the key that is in use.
- * @param {Number} [options.charCode=0] The Unicode code for the character
- * associated with the key being used.
- * @param {Window} [view=window] The view containing the target. This is typically
- * the window object.
- * @private
- */
- injectKeyEvent: function(target, options, view) {
- var type = options.type,
- customEvent = null;
- if (type === 'textevent') {
- type = 'keypress';
- }
- view = view || window;
- // check for DOM-compliant browsers first
- if (doc.createEvent) {
- try {
- customEvent = doc.createEvent("KeyEvents");
- // Interesting problem: Firefox implemented a non-standard
- // version of initKeyEvent() based on DOM Level 2 specs.
- // Key event was removed from DOM Level 2 and re-introduced
- // in DOM Level 3 with a different interface. Firefox is the
- // only browser with any implementation of Key Events, so for
- // now, assume it's Firefox if the above line doesn't error.
- // @TODO: Decipher between Firefox's implementation and a correct one.
- customEvent.initKeyEvent(type, options.bubbles, options.cancelable, view, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.keyCode, options.charCode);
- } catch (ex) {
- // If it got here, that means key events aren't officially supported.
- // Safari/WebKit is a real problem now. WebKit 522 won't let you
- // set keyCode, charCode, or other properties if you use a
- // UIEvent, so we first must try to create a generic event. The
- // fun part is that this will throw an error on Safari 2.x. The
- // end result is that we need another try...catch statement just to
- // deal with this mess.
- try {
- // try to create generic event - will fail in Safari 2.x
- customEvent = doc.createEvent("Events");
- } catch (uierror) {
- // the above failed, so create a UIEvent for Safari 2.x
- customEvent = doc.createEvent("UIEvents");
- } finally {
- customEvent.initEvent(type, options.bubbles, options.cancelable);
- customEvent.view = view;
- customEvent.altKey = options.altKey;
- customEvent.ctrlKey = options.ctrlKey;
- customEvent.shiftKey = options.shiftKey;
- customEvent.metaKey = options.metaKey;
- customEvent.keyCode = options.keyCode;
- customEvent.charCode = options.charCode;
- }
- }
- target.dispatchEvent(customEvent);
- } else if (doc.createEventObject) {
- // IE
- customEvent = doc.createEventObject();
- customEvent.bubbles = options.bubbles;
- customEvent.cancelable = options.cancelable;
- customEvent.view = view;
- customEvent.ctrlKey = options.ctrlKey;
- customEvent.altKey = options.altKey;
- customEvent.shiftKey = options.shiftKey;
- customEvent.metaKey = options.metaKey;
- // IE doesn't support charCode explicitly. CharCode should
- // take precedence over any keyCode value for accurate
- // representation.
- customEvent.keyCode = (options.charCode > 0) ? options.charCode : options.keyCode;
- target.fireEvent("on" + type, customEvent);
- } else {
- return false;
- }
- return true;
- },
- /**
- * Injects a mouse event using the given event information to populate the event
- * object.
- *
- * @param {HTMLElement} target The target of the given event.
- * @param {Object} options Object object containing all of the event injection
- * options.
- * @param {String} options.type The type of event to fire. This can be any one of
- * the following: `click`, `dblclick`, `mousedown`, `mouseup`, `mouseout`,
- * `mouseover` and `mousemove`.
- * @param {Boolean} [options.bubbles=true] `tru` if the event can be bubbled up.
- * DOM Level 2 specifies that all mouse events bubble by default.
- * @param {Boolean} [options.cancelable=true] `true` if the event can be canceled
- * using `preventDefault`. DOM Level 2 specifies that all mouse events except
- * `mousemove` can be cancelled. This defaults to `false` for `mousemove`.
- * @param {Boolean} [options.ctrlKey=false] `true` if one of the CTRL keys is
- * pressed while the event is firing.
- * @param {Boolean} [options.altKey=false] `true` if one of the ALT keys is
- * pressed while the event is firing.
- * @param {Boolean} [options.shiftKey=false] `true` if one of the SHIFT keys is
- * pressed while the event is firing.
- * @param {Boolean} [options.metaKey=false] `true` if one of the META keys is
- * pressed while the event is firing.
- * @param {Number} [options.detail=1] The number of times the mouse button has
- * been used.
- * @param {Number} [options.screenX=0] The x-coordinate on the screen at which point
- * the event occurred.
- * @param {Number} [options.screenY=0] The y-coordinate on the screen at which point
- * the event occurred.
- * @param {Number} [options.clientX=0] The x-coordinate on the client at which point
- * the event occurred.
- * @param {Number} [options.clientY=0] The y-coordinate on the client at which point
- * the event occurred.
- * @param {Number} [options.button=0] The button being pressed while the event is
- * executing. The value should be 0 for the primary mouse button (typically the
- * left button), 1 for the tertiary mouse button (typically the middle button),
- * and 2 for the secondary mouse button (typically the right button).
- * @param {HTMLElement} [options.relatedTarget=null] For `mouseout` events, this
- * is the element that the mouse has moved to. For `mouseover` events, this is
- * the element that the mouse has moved from. This argument is ignored for all
- * other events.
- * @param {Window} [view=window] The view containing the target. This is typically
- * the window object.
- * @private
- */
- injectMouseEvent: function(target, options, view) {
- var type = options.type,
- customEvent = null;
- view = view || window;
- // check for DOM-compliant browsers first
- if (doc.createEvent) {
- customEvent = doc.createEvent("MouseEvents");
- // Safari 2.x (WebKit 418) still doesn't implement initMouseEvent()
- if (customEvent.initMouseEvent) {
- 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);
- } else {
- // Safari
- // the closest thing available in Safari 2.x is UIEvents
- customEvent = doc.createEvent("UIEvents");
- customEvent.initEvent(type, options.bubbles, options.cancelable);
- customEvent.view = view;
- customEvent.detail = options.detail;
- customEvent.screenX = options.screenX;
- customEvent.screenY = options.screenY;
- customEvent.clientX = options.clientX;
- customEvent.clientY = options.clientY;
- customEvent.ctrlKey = options.ctrlKey;
- customEvent.altKey = options.altKey;
- customEvent.metaKey = options.metaKey;
- customEvent.shiftKey = options.shiftKey;
- customEvent.button = options.button;
- customEvent.relatedTarget = options.relatedTarget;
- }
- /*
- * Check to see if relatedTarget has been assigned. Firefox
- * versions less than 2.0 don't allow it to be assigned via
- * initMouseEvent() and the property is readonly after event
- * creation, so in order to keep YAHOO.util.getRelatedTarget()
- * working, assign to the IE proprietary toElement property
- * for mouseout event and fromElement property for mouseover
- * event.
- */
- if (options.relatedTarget && !customEvent.relatedTarget) {
- if (type === "mouseout") {
- customEvent.toElement = options.relatedTarget;
- } else if (type === "mouseover") {
- customEvent.fromElement = options.relatedTarget;
- }
- }
- target.dispatchEvent(customEvent);
- } else if (doc.createEventObject) {
- // IE
- customEvent = doc.createEventObject();
- customEvent.bubbles = options.bubbles;
- customEvent.cancelable = options.cancelable;
- customEvent.view = view;
- customEvent.detail = options.detail;
- customEvent.screenX = options.screenX;
- customEvent.screenY = options.screenY;
- customEvent.clientX = options.clientX;
- customEvent.clientY = options.clientY;
- customEvent.ctrlKey = options.ctrlKey;
- customEvent.altKey = options.altKey;
- customEvent.metaKey = options.metaKey;
- customEvent.shiftKey = options.shiftKey;
- customEvent.button = Player.ieButtonCodeMap[options.button] || 0;
- /*
- * Have to use relatedTarget because IE won't allow assignment
- * to toElement or fromElement on generic events. This keeps
- * YAHOO.util.customEvent.getRelatedTarget() functional.
- */
- customEvent.relatedTarget = options.relatedTarget;
- target.fireEvent('on' + type, customEvent);
- } else {
- return false;
- }
- return true;
- },
- /**
- * Injects a UI event using the given event information to populate the event
- * object.
- *
- * @param {HTMLElement} target The target of the given event.
- * @param {Object} options
- * @param {String} options.type The type of event to fire. This can be any one of
- * the following: `click`, `dblclick`, `mousedown`, `mouseup`, `mouseout`,
- * `mouseover` and `mousemove`.
- * @param {Boolean} [options.bubbles=true] `tru` if the event can be bubbled up.
- * DOM Level 2 specifies that all mouse events bubble by default.
- * @param {Boolean} [options.cancelable=true] `true` if the event can be canceled
- * using `preventDefault`. DOM Level 2 specifies that all mouse events except
- * `mousemove` can be canceled. This defaults to `false` for `mousemove`.
- * @param {Number} [options.detail=1] The number of times the mouse button has been
- * used.
- * @param {Window} [view=window] The view containing the target. This is typically
- * the window object.
- * @private
- */
- injectUIEvent: function(target, options, view) {
- var customEvent = null;
- view = view || window;
- // check for DOM-compliant browsers first
- if (doc.createEvent) {
- // just a generic UI Event object is needed
- customEvent = doc.createEvent("UIEvents");
- customEvent.initUIEvent(options.type, options.bubbles, options.cancelable, view, options.detail);
- target.dispatchEvent(customEvent);
- } else if (doc.createEventObject) {
- // IE
- customEvent = doc.createEventObject();
- customEvent.bubbles = options.bubbles;
- customEvent.cancelable = options.cancelable;
- customEvent.view = view;
- customEvent.detail = options.detail;
- target.fireEvent("on" + options.type, customEvent);
- } else {
- return false;
- }
- return true;
- }
- }
- };
- });
- // statics
- /**
- * @extends Ext.ux.event.Driver
- * Event recorder.
- */
- Ext.define('Ext.ux.event.Recorder', function(Recorder) {
- var eventsToRecord, eventKey;
- function apply() {
- var a = arguments,
- n = a.length,
- obj = {
- kind: 'other'
- },
- i;
- for (i = 0; i < n; ++i) {
- Ext.apply(obj, arguments[i]);
- }
- if (obj.alt && !obj.event) {
- obj.event = obj.alt;
- }
- return obj;
- }
- function key(extra) {
- return apply({
- kind: 'keyboard',
- modKeys: true,
- key: true
- }, extra);
- }
- function mouse(extra) {
- return apply({
- kind: 'mouse',
- button: true,
- modKeys: true,
- xy: true
- }, extra);
- }
- eventsToRecord = {
- keydown: key(),
- keypress: key(),
- keyup: key(),
- dragmove: mouse({
- alt: 'mousemove',
- pageCoords: true,
- whileDrag: true
- }),
- mousemove: mouse({
- pageCoords: true
- }),
- mouseover: mouse(),
- mouseout: mouse(),
- click: mouse(),
- wheel: mouse({
- wheel: true
- }),
- mousedown: mouse({
- press: true
- }),
- mouseup: mouse({
- release: true
- }),
- scroll: apply({
- listen: false
- }),
- focus: apply(),
- blur: apply()
- };
- for (eventKey in eventsToRecord) {
- if (!eventsToRecord[eventKey].event) {
- eventsToRecord[eventKey].event = eventKey;
- }
- }
- eventsToRecord.wheel.event = null;
- // must detect later
- return {
- extend: 'Ext.ux.event.Driver',
- /**
- * @event add
- * Fires when an event is added to the recording.
- * @param {Ext.ux.event.Recorder} this
- * @param {Object} eventDescriptor The event descriptor.
- */
- /**
- * @event coalesce
- * Fires when an event is coalesced. This edits the tail of the recorded
- * event list.
- * @param {Ext.ux.event.Recorder} this
- * @param {Object} eventDescriptor The event descriptor that was coalesced.
- */
- eventsToRecord: eventsToRecord,
- ignoreIdRegEx: /ext-gen(?:\d+)/,
- inputRe: /^(input|textarea)$/i,
- constructor: function(config) {
- var me = this,
- events = config && config.eventsToRecord;
- if (events) {
- me.eventsToRecord = Ext.apply(Ext.apply({}, me.eventsToRecord), // duplicate
- events);
- // and merge
- delete config.eventsToRecord;
- }
- // don't smash
- me.callParent(arguments);
- me.clear();
- me.modKeys = [];
- me.attachTo = me.attachTo || window;
- },
- clear: function() {
- this.eventsRecorded = [];
- },
- listenToEvent: function(event) {
- var me = this,
- el = me.attachTo.document.body,
- fn = function() {
- return me.onEvent.apply(me, arguments);
- },
- cleaner = {};
- if (el.attachEvent && el.ownerDocument.documentMode < 10) {
- event = 'on' + event;
- el.attachEvent(event, fn);
- cleaner.destroy = function() {
- if (fn) {
- el.detachEvent(event, fn);
- fn = null;
- }
- };
- } else {
- el.addEventListener(event, fn, true);
- cleaner.destroy = function() {
- if (fn) {
- el.removeEventListener(event, fn, true);
- fn = null;
- }
- };
- }
- return cleaner;
- },
- coalesce: function(rec, ev) {
- var me = this,
- events = me.eventsRecorded,
- length = events.length,
- tail = length && events[length - 1],
- tail2 = (length > 1) && events[length - 2],
- tail3 = (length > 2) && events[length - 3];
- if (!tail) {
- return false;
- }
- if (rec.type === 'mousemove') {
- if (tail.type === 'mousemove' && rec.ts - tail.ts < 200) {
- rec.ts = tail.ts;
- events[length - 1] = rec;
- return true;
- }
- } else if (rec.type === 'click') {
- if (tail2 && tail.type === 'mouseup' && tail2.type === 'mousedown') {
- 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)) {
- events.pop();
- // remove mouseup
- tail2.type = 'mduclick';
- return true;
- }
- }
- } else if (rec.type === 'keyup') {
- // tail3 = { type: "type", text: "..." },
- // tail2 = { type: "keydown", charCode: 65, keyCode: 65 },
- // tail = { type: "keypress", charCode: 97, keyCode: 97 },
- // rec = { type: "keyup", charCode: 65, keyCode: 65 },
- if (tail2 && tail.type === 'keypress' && tail2.type === 'keydown') {
- if (rec.target === tail.target && rec.target === tail2.target) {
- events.pop();
- // remove keypress
- tail2.type = 'type';
- tail2.text = String.fromCharCode(tail.charCode);
- delete tail2.charCode;
- delete tail2.keyCode;
- if (tail3 && tail3.type === 'type') {
- if (tail3.text && tail3.target === tail2.target) {
- tail3.text += tail2.text;
- events.pop();
- }
- }
- return true;
- }
- }
- // tail = { type: "keydown", charCode: 40, keyCode: 40 },
- // rec = { type: "keyup", charCode: 40, keyCode: 40 },
- else if (me.completeKeyStroke(tail, rec)) {
- tail.type = 'type';
- me.completeSpecialKeyStroke(ev.target, tail, rec);
- return true;
- }
- // tail2 = { type: "keydown", charCode: 40, keyCode: 40 },
- // tail = { type: "scroll", ... },
- // rec = { type: "keyup", charCode: 40, keyCode: 40 },
- else if (tail.type === 'scroll' && me.completeKeyStroke(tail2, rec)) {
- tail2.type = 'type';
- me.completeSpecialKeyStroke(ev.target, tail2, rec);
- // swap the order of type and scroll events
- events.pop();
- events.pop();
- events.push(tail, tail2);
- return true;
- }
- }
- return false;
- },
- completeKeyStroke: function(down, up) {
- if (down && down.type === 'keydown' && down.keyCode === up.keyCode) {
- delete down.charCode;
- return true;
- }
- return false;
- },
- completeSpecialKeyStroke: function(target, down, up) {
- var key = this.specialKeysByCode[up.keyCode];
- if (key && this.inputRe.test(target.tagName)) {
- // home,end,arrow keys + shift get crazy, so encode selection/caret
- delete down.keyCode;
- down.key = key;
- down.selection = this.getTextSelection(target);
- if (down.selection[0] === down.selection[1]) {
- down.caret = down.selection[0];
- delete down.selection;
- }
- return true;
- }
- return false;
- },
- getElementXPath: function(el) {
- var me = this,
- good = false,
- xpath = [],
- count, sibling, t, tag;
- for (t = el; t; t = t.parentNode) {
- if (t === me.attachTo.document.body) {
- xpath.unshift('~');
- good = true;
- break;
- }
- if (t.id && !me.ignoreIdRegEx.test(t.id)) {
- xpath.unshift('#' + t.id);
- good = true;
- break;
- }
- for (count = 1 , sibling = t; !!(sibling = sibling.previousSibling); ) {
- if (sibling.tagName === t.tagName) {
- ++count;
- }
- }
- tag = t.tagName.toLowerCase();
- if (count < 2) {
- xpath.unshift(tag);
- } else {
- xpath.unshift(tag + '[' + count + ']');
- }
- }
- return good ? xpath.join('/') : null;
- },
- getRecordedEvents: function() {
- return this.eventsRecorded;
- },
- onEvent: function(ev) {
- var me = this,
- e = new Ext.event.Event(ev),
- info = me.eventsToRecord[e.type],
- root, modKeys, elXY,
- rec = {
- type: e.type,
- ts: me.getTimestamp(),
- target: me.getElementXPath(e.target)
- },
- xy;
- if (!info || !rec.target) {
- return;
- }
- root = e.target.ownerDocument;
- root = root.defaultView || root.parentWindow;
- // Standards || IE
- if (root !== me.attachTo) {
- return;
- }
- if (me.eventsToRecord.scroll) {
- me.syncScroll(e.target);
- }
- if (info.xy) {
- xy = e.getXY();
- if (info.pageCoords || !rec.target) {
- rec.px = xy[0];
- rec.py = xy[1];
- } else {
- elXY = Ext.fly(e.getTarget()).getXY();
- xy[0] -= elXY[0];
- xy[1] -= elXY[1];
- rec.x = xy[0];
- rec.y = xy[1];
- }
- }
- if (info.button) {
- if ('buttons' in ev) {
- rec.button = ev.buttons;
- } else // LEFT=1, RIGHT=2, MIDDLE=4, etc.
- {
- rec.button = ev.button;
- }
- if (!rec.button && info.whileDrag) {
- return;
- }
- }
- if (info.wheel) {
- rec.type = 'wheel';
- if (info.event === 'wheel') {
- // Current FireFox (technically IE9+ if we use addEventListener but
- // checking document.onwheel does not detect this)
- rec.dx = ev.deltaX;
- rec.dy = ev.deltaY;
- } else if (typeof ev.wheelDeltaX === 'number') {
- // new WebKit has both X & Y
- rec.dx = -1 / 40 * ev.wheelDeltaX;
- rec.dy = -1 / 40 * ev.wheelDeltaY;
- } else if (ev.wheelDelta) {
- // old WebKit and IE
- rec.dy = -1 / 40 * ev.wheelDelta;
- } else if (ev.detail) {
- // Old Gecko
- rec.dy = ev.detail;
- }
- }
- if (info.modKeys) {
- me.modKeys[0] = e.altKey ? 'A' : '';
- me.modKeys[1] = e.ctrlKey ? 'C' : '';
- me.modKeys[2] = e.metaKey ? 'M' : '';
- me.modKeys[3] = e.shiftKey ? 'S' : '';
- modKeys = me.modKeys.join('');
- if (modKeys) {
- rec.modKeys = modKeys;
- }
- }
- if (info.key) {
- rec.charCode = e.getCharCode();
- rec.keyCode = e.getKey();
- }
- if (me.coalesce(rec, e)) {
- me.fireEvent('coalesce', me, rec);
- } else {
- me.eventsRecorded.push(rec);
- me.fireEvent('add', me, rec);
- }
- },
- onStart: function() {
- var me = this,
- ddm = me.attachTo.Ext.dd.DragDropManager,
- evproto = me.attachTo.Ext.EventObjectImpl.prototype,
- special = [];
- // FireFox does not support the 'mousewheel' event but does support the
- // 'wheel' event instead.
- Recorder.prototype.eventsToRecord.wheel.event = ('onwheel' in me.attachTo.document) ? 'wheel' : 'mousewheel';
- me.listeners = [];
- Ext.Object.each(me.eventsToRecord, function(name, value) {
- if (value && value.listen !== false) {
- if (!value.event) {
- value.event = name;
- }
- if (value.alt && value.alt !== name) {
- // The 'drag' event is just mousemove while buttons are pressed,
- // so if there is a mousemove entry as well, ignore the drag
- if (!me.eventsToRecord[value.alt]) {
- special.push(value);
- }
- } else {
- me.listeners.push(me.listenToEvent(value.event));
- }
- }
- });
- Ext.each(special, function(info) {
- me.eventsToRecord[info.alt] = info;
- me.listeners.push(me.listenToEvent(info.alt));
- });
- me.ddmStopEvent = ddm.stopEvent;
- ddm.stopEvent = Ext.Function.createSequence(ddm.stopEvent, function(e) {
- me.onEvent(e);
- });
- me.evStopEvent = evproto.stopEvent;
- evproto.stopEvent = Ext.Function.createSequence(evproto.stopEvent, function() {
- me.onEvent(this);
- });
- },
- onStop: function() {
- var me = this;
- Ext.destroy(me.listeners);
- me.listeners = null;
- me.attachTo.Ext.dd.DragDropManager.stopEvent = me.ddmStopEvent;
- me.attachTo.Ext.EventObjectImpl.prototype.stopEvent = me.evStopEvent;
- },
- samePt: function(pt1, pt2) {
- return pt1.x === pt2.x && pt1.y === pt2.y;
- },
- syncScroll: function(el) {
- var me = this,
- ts = me.getTimestamp(),
- oldX, oldY, x, y, scrolled, rec, p;
- for (p = el; p; p = p.parentNode) {
- oldX = p.$lastScrollLeft;
- oldY = p.$lastScrollTop;
- x = p.scrollLeft;
- y = p.scrollTop;
- scrolled = false;
- if (oldX !== x) {
- if (x) {
- scrolled = true;
- }
- p.$lastScrollLeft = x;
- }
- if (oldY !== y) {
- if (y) {
- scrolled = true;
- }
- p.$lastScrollTop = y;
- }
- if (scrolled) {
- // console.log('scroll x:' + x + ' y:' + y, p);
- me.eventsRecorded.push(rec = {
- type: 'scroll',
- target: me.getElementXPath(p),
- ts: ts,
- pos: [
- x,
- y
- ]
- });
- me.fireEvent('add', me, rec);
- }
- if (p.tagName === 'BODY') {
- break;
- }
- }
- }
- };
- });
- /**
- * Describes a gauge needle as a shape defined in SVG path syntax.
- *
- * Note: this class and its subclasses are not supposed to be instantiated directly
- * - an object should be passed the gauge's {@link Ext.ux.gauge.Gauge#needle}
- * config instead. Needle instances are also not supposed to be moved
- * between gauges.
- */
- Ext.define('Ext.ux.gauge.needle.Abstract', {
- mixins: [
- 'Ext.mixin.Factoryable'
- ],
- alias: 'gauge.needle.abstract',
- isNeedle: true,
- config: {
- /**
- * The generator function for the needle's shape.
- * Because the gauge component is resizable, and it is generally
- * desirable to resize the needle along with the gauge, the needle's
- * shape should have an ability to grow, typically non-uniformly,
- * which necessitates a generator function that will update the needle's
- * path, so that its proportions are appropriate for the current gauge size.
- *
- * The generator function is given two parameters: the inner and outer
- * radius of the needle. For example, for a straight arrow, the path
- * definition is expected to have the base of the needle at the origin
- * - (0, 0) coordinates - and point downwards. The needle will be automatically
- * translated to the center of the gauge and rotated to represent the current
- * gauge {@link Ext.ux.gauge.Gauge#value value}.
- *
- * @param {Function} path The path generator function.
- * @param {Number} path.innerRadius The function's first parameter.
- * @param {Number} path.outerRadius The function's second parameter.
- * @return {String} path.return The shape of the needle in the SVG path syntax returned by
- * the generator function.
- */
- path: null,
- /**
- * The inner radius of the needle. This works just like the `innerRadius`
- * config of the {@link Ext.ux.gauge.Gauge#trackStyle}.
- * The default value is `25` to make sure the needle doesn't overlap with
- * the value of the gauge shown at its center by default.
- *
- * @param {Number/String} [innerRadius=25]
- */
- innerRadius: 25,
- /**
- * The outer radius of the needle. This works just like the `outerRadius`
- * config of the {@link Ext.ux.gauge.Gauge#trackStyle}.
- *
- * @param {Number/String} [outerRadius='100% - 20']
- */
- outerRadius: '100% - 20',
- /**
- * The shape generated by the {@link #path} function is used as the value
- * for the `d` attribute of the SVG `<path>` element. This element
- * has the default class name of `.x-gauge-needle`, so that CSS can be used
- * to give all gauge needles some common styling. To style a particular needle,
- * one can use this config to add styles to the needle's `<path>` element directly,
- * or use a custom {@link Ext.ux.gauge.Gauge#cls class} for the needle's gauge
- * and style the needle from there.
- *
- * This config is not supposed to be updated manually, the styles should
- * always be updated by the means of the `setStyle` call. For example,
- * this is not allowed:
- *
- * gauge.getStyle().fill = 'red'; // WRONG!
- * gauge.setStyle({ 'fill': 'red' }); // correct
- *
- * Subsequent calls to the `setStyle` will add to the styles set previously
- * or overwrite their values, but won't remove them. If you'd like to style
- * from a clean slate, setting the style to `null` first will remove the styles
- * previously set:
- *
- * gauge.getNeedle().setStyle(null);
- *
- * If an SVG shape was produced by a designer rather than programmatically,
- * in other words, the {@link #path} function returns the same shape regardless
- * of the parameters it was given, the uniform scaling of said shape is the only
- * option, if one wants to use gauges of different sizes. In this case,
- * it's possible to specify the desired scale by using the `transform` style,
- * for example:
- *
- * transform: 'scale(0.35)'
- *
- * @param {Object} style
- */
- style: null,
- /**
- * @private
- * @param {Number} radius
- */
- radius: 0,
- /**
- * @private
- * Expected in the initial config, required during construction.
- * @param {Ext.ux.gauge.Gauge} gauge
- */
- gauge: null
- },
- constructor: function(config) {
- this.initConfig(config);
- },
- applyInnerRadius: function(innerRadius) {
- return this.getGauge().getRadiusFn(innerRadius);
- },
- applyOuterRadius: function(outerRadius) {
- return this.getGauge().getRadiusFn(outerRadius);
- },
- updateRadius: function() {
- this.regeneratePath();
- },
- setTransform: function(centerX, centerY, rotation) {
- var needleGroup = this.getNeedleGroup();
- needleGroup.setStyle('transform', 'translate(' + centerX + 'px,' + centerY + 'px) ' + 'rotate(' + rotation + 'deg)');
- },
- applyPath: function(path) {
- return Ext.isFunction(path) ? path : null;
- },
- updatePath: function(path) {
- this.regeneratePath(path);
- },
- regeneratePath: function(path) {
- path = path || this.getPath();
- // eslint-disable-next-line vars-on-top
- var me = this,
- radius = me.getRadius(),
- inner = me.getInnerRadius()(radius),
- outer = me.getOuterRadius()(radius),
- d = outer > inner ? path(inner, outer) : '';
- me.getNeedlePath().dom.setAttribute('d', d);
- },
- getNeedleGroup: function() {
- var gauge = this.getGauge(),
- group = this.needleGroup;
- // The gauge positions the needle by calling its `setTransform` method,
- // which applies a transformation to the needle's group, that contains
- // the actual path element. This is done because we need the ability to
- // transform the path independently from it's position in the gauge.
- // For example, if the needle has to be made bigger, is shouldn't be
- // part of the transform that centers it in the gauge and rotates it
- // to point at the current value.
- if (!group) {
- group = this.needleGroup = Ext.get(document.createElementNS(gauge.svgNS, 'g'));
- gauge.getSvg().appendChild(group);
- }
- return group;
- },
- getNeedlePath: function() {
- var me = this,
- pathElement = me.pathElement;
- if (!pathElement) {
- pathElement = me.pathElement = Ext.get(document.createElementNS(me.getGauge().svgNS, 'path'));
- pathElement.dom.setAttribute('class', Ext.baseCSSPrefix + 'gauge-needle');
- me.getNeedleGroup().appendChild(pathElement);
- }
- return pathElement;
- },
- updateStyle: function(style) {
- var pathElement = this.getNeedlePath();
- // Note that we are setting the `style` attribute, e.g `style="fill: red"`,
- // instead of path attributes individually, e.g. `fill="red"` because
- // the attribute styles defined in CSS classes will override the values
- // of attributes set on the elements individually.
- if (Ext.isObject(style)) {
- pathElement.setStyle(style);
- } else {
- pathElement.dom.removeAttribute('style');
- }
- },
- destroy: function() {
- var me = this;
- me.pathElement = Ext.destroy(me.pathElement);
- me.needleGroup = Ext.destroy(me.needleGroup);
- me.setGauge(null);
- }
- });
- /**
- * Displays a value within the given interval as a gauge. For example:
- *
- * @example
- * Ext.create({
- * xtype: 'panel',
- * renderTo: document.body,
- * width: 200,
- * height: 200,
- * layout: 'fit',
- * items: {
- * xtype: 'gauge',
- * padding: 20,
- * value: 55,
- * minValue: 40,
- * maxValue: 80
- * }
- * });
- *
- * It's also possible to use gauges to create loading indicators:
- *
- * @example
- * Ext.create({
- * xtype: 'panel',
- * renderTo: document.body,
- * width: 200,
- * height: 200,
- * layout: 'fit',
- * items: {
- * xtype: 'gauge',
- * padding: 20,
- * trackStart: 0,
- * trackLength: 360,
- * value: 20,
- * valueStyle: {
- * round: true
- * },
- * textTpl: 'Loading...',
- * animation: {
- * easing: 'linear',
- * duration: 100000
- * }
- * }
- * }).items.first().setAngleOffset(360 * 100);
- *
- * Gauges can contain needles as well.
- *
- * @example
- * Ext.create({
- * xtype: 'panel',
- * renderTo: document.body,
- * width: 200,
- * height: 200,
- * layout: 'fit',
- * items: {
- * xtype: 'gauge',
- * padding: 20,
- * value: 55,
- * minValue: 40,
- * maxValue: 80,
- * needle: 'wedge'
- * }
- * });
- *
- */
- Ext.define('Ext.ux.gauge.Gauge', {
- alternateClassName: 'Ext.ux.Gauge',
- extend: 'Ext.Gadget',
- xtype: 'gauge',
- requires: [
- 'Ext.ux.gauge.needle.Abstract',
- 'Ext.util.Region'
- ],
- config: {
- /**
- * @cfg {Number/String} padding
- * Gauge sector padding in pixels or percent of width/height, whichever is smaller.
- */
- padding: 10,
- /**
- * @cfg {Number} trackStart
- * The angle in the [0, 360) interval at which the gauge's track sector starts.
- * E.g. 0 for 3 o-clock, 90 for 6 o-clock, 180 for 9 o-clock, 270 for noon.
- */
- trackStart: 135,
- /**
- * @cfg {Number} trackLength
- * The angle in the (0, 360] interval to add to the {@link #trackStart} angle
- * to determine the angle at which the track ends.
- */
- trackLength: 270,
- /**
- * @cfg {Number} angleOffset
- * The angle at which the {@link #minValue} starts in case of a circular gauge.
- */
- angleOffset: 0,
- /**
- * @cfg {Number} minValue
- * The minimum value that the gauge can represent.
- */
- minValue: 0,
- /**
- * @cfg {Number} maxValue
- * The maximum value that the gauge can represent.
- */
- maxValue: 100,
- /**
- * @cfg {Number} value
- * The current value of the gauge.
- */
- value: 50,
- /**
- * @cfg {Ext.ux.gauge.needle.Abstract} needle
- * A config object for the needle to be used by the gauge.
- * The needle will track the current {@link #value}.
- * The default needle type is 'diamond', so if a config like
- *
- * needle: {
- * outerRadius: '100%'
- * }
- *
- * is used, the app/view still has to require
- * the `Ext.ux.gauge.needle.Diamond` class.
- * If a type is specified explicitly
- *
- * needle: {
- * type: 'arrow'
- * }
- *
- * it's straightforward which class should be required.
- */
- needle: null,
- needleDefaults: {
- cached: true,
- $value: {
- type: 'diamond'
- }
- },
- /**
- * @cfg {Boolean} [clockwise=true]
- * `true` - {@link #cfg!value} increments in a clockwise fashion
- * `false` - {@link #cfg!value} increments in an anticlockwise fashion
- */
- clockwise: true,
- /**
- * @cfg {Ext.XTemplate} textTpl
- * The template for the text in the center of the gauge.
- * The available data values are:
- * - `value` - The {@link #cfg!value} of the gauge.
- * - `percent` - The value as a percentage between 0 and 100.
- * - `minValue` - The value of the {@link #cfg!minValue} config.
- * - `maxValue` - The value of the {@link #cfg!maxValue} config.
- * - `delta` - The delta between the {@link #cfg!minValue} and {@link #cfg!maxValue}.
- */
- textTpl: [
- '<tpl>{value:number("0.00")}%</tpl>'
- ],
- /**
- * @cfg {String} [textAlign='c-c']
- * If the gauge has a donut hole, the text will be centered inside it.
- * Otherwise, the text will be centered in the middle of the gauge's
- * bounding box. This config allows to alter the position of the text
- * in the latter case. See the docs for the `align` option to the
- * {@link Ext.util.Region#alignTo} method for possible ways of alignment
- * of the text to the guage's bounding box.
- */
- textAlign: 'c-c',
- /**
- * @cfg {Object} textOffset
- * This config can be used to displace the {@link #textTpl text} from its default
- * position in the center of the gauge by providing values for horizontal and
- * vertical displacement.
- * @cfg {Number} textOffset.dx Horizontal displacement.
- * @cfg {Number} textOffset.dy Vertical displacement.
- */
- textOffset: {
- dx: 0,
- dy: 0
- },
- /**
- * @cfg {Object} trackStyle
- * Track sector styles.
- * @cfg {String/Object[]} trackStyle.fill Track sector fill color. Defaults to CSS value.
- * It's also possible to have a linear gradient fill that starts at the top-left corner
- * of the gauge and ends at its bottom-right corner, by providing an array of color stop
- * objects. For example:
- *
- * trackStyle: {
- * fill: [{
- * offset: 0,
- * color: 'green',
- * opacity: 0.8
- * }, {
- * offset: 1,
- * color: 'gold'
- * }]
- * }
- *
- * @cfg {Number} trackStyle.fillOpacity Track sector fill opacity. Defaults to CSS value.
- * @cfg {String} trackStyle.stroke Track sector stroke color. Defaults to CSS value.
- * @cfg {Number} trackStyle.strokeOpacity Track sector stroke opacity.
- * Defaults to CSS value.
- * @cfg {Number} trackStyle.strokeWidth Track sector stroke width. Defaults to CSS value.
- * @cfg {Number/String} [trackStyle.outerRadius='100%'] The outer radius of the track
- * sector.
- * For example:
- *
- * outerRadius: '90%', // 90% of the maximum radius
- * outerRadius: 100, // radius of 100 pixels
- * outerRadius: '70% + 5', // 70% of the maximum radius plus 5 pixels
- * outerRadius: '80% - 10', // 80% of the maximum radius minus 10 pixels
- *
- * @cfg {Number/String} [trackStyle.innerRadius='50%'] The inner radius of the track sector.
- * See the `trackStyle.outerRadius` config documentation for more information.
- * @cfg {Boolean} [trackStyle.round=false] Whether to round the track sector edges or not.
- */
- trackStyle: {
- outerRadius: '100%',
- innerRadius: '100% - 20',
- round: false
- },
- /**
- * @cfg {Object} valueStyle
- * Value sector styles.
- * @cfg {String/Object[]} valueStyle.fill Value sector fill color. Defaults to CSS value.
- * See the `trackStyle.fill` config documentation for more information.
- * @cfg {Number} valueStyle.fillOpacity Value sector fill opacity. Defaults to CSS value.
- * @cfg {String} valueStyle.stroke Value sector stroke color. Defaults to CSS value.
- * @cfg {Number} valueStyle.strokeOpacity Value sector stroke opacity. Defaults to
- * CSS value.
- * @cfg {Number} valueStyle.strokeWidth Value sector stroke width. Defaults to CSS value.
- * @cfg {Number/String} [valueStyle.outerRadius='100% - 4'] The outer radius of the value
- * sector.
- * See the `trackStyle.outerRadius` config documentation for more information.
- * @cfg {Number/String} [valueStyle.innerRadius='50% + 4'] The inner radius of the value
- * sector.
- * See the `trackStyle.outerRadius` config documentation for more information.
- * @cfg {Boolean} [valueStyle.round=false] Whether to round the value sector edges or not.
- */
- valueStyle: {
- outerRadius: '100% - 2',
- innerRadius: '100% - 18',
- round: false
- },
- /**
- * @cfg {Object/Boolean} [animation=true]
- * The animation applied to the gauge on changes to the {@link #value}
- * and the {@link #angleOffset} configs. Defaults to 1 second animation
- * with the 'out' easing.
- * @cfg {Number} animation.duration The duraction of the animation.
- * @cfg {String} animation.easing The easing function to use for the animation.
- * Possible values are:
- * - `linear` - no easing, no acceleration
- * - `in` - accelerating from zero velocity
- * - `out` - (default) decelerating to zero velocity
- * - `inOut` - acceleration until halfway, then deceleration
- */
- animation: true
- },
- baseCls: Ext.baseCSSPrefix + 'gauge',
- template: [
- {
- reference: 'bodyElement',
- children: [
- {
- reference: 'textElement',
- cls: Ext.baseCSSPrefix + 'gauge-text'
- }
- ]
- }
- ],
- defaultBindProperty: 'value',
- pathAttributes: {
- // The properties in the `trackStyle` and `valueStyle` configs
- // that are path attributes.
- fill: true,
- fillOpacity: true,
- stroke: true,
- strokeOpacity: true,
- strokeWidth: true
- },
- easings: {
- linear: Ext.identityFn,
- // cubic easings
- 'in': function(t) {
- return t * t * t;
- },
- out: function(t) {
- return (--t) * t * t + 1;
- },
- inOut: function(t) {
- return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;
- }
- },
- resizeDelay: 0,
- // in milliseconds
- resizeTimerId: 0,
- size: null,
- // cached size
- svgNS: 'http://www.w3.org/2000/svg',
- svg: null,
- // SVG document
- defs: null,
- // the `defs` section of the SVG document
- trackArc: null,
- valueArc: null,
- trackGradient: null,
- valueGradient: null,
- fx: null,
- // either the `value` or the `angleOffset` animation
- fxValue: 0,
- // the actual value rendered/animated
- fxAngleOffset: 0,
- constructor: function(config) {
- var me = this;
- me.fitSectorInRectCache = {
- startAngle: null,
- lengthAngle: null,
- minX: null,
- maxX: null,
- minY: null,
- maxY: null
- };
- me.interpolator = me.createInterpolator();
- me.callParent([
- config
- ]);
- me.el.on('resize', 'onElementResize', me);
- },
- doDestroy: function() {
- var me = this;
- Ext.undefer(me.resizeTimerId);
- me.el.un('resize', 'onElementResize', me);
- me.stopAnimation();
- me.setNeedle(null);
- me.trackGradient = Ext.destroy(me.trackGradient);
- me.valueGradient = Ext.destroy(me.valueGradient);
- me.defs = Ext.destroy(me.defs);
- me.svg = Ext.destroy(me.svg);
- me.callParent();
- },
- // <if classic>
- afterComponentLayout: function(width, height, oldWidth, oldHeight) {
- this.callParent([
- width,
- height,
- oldWidth,
- oldHeight
- ]);
- if (Ext.isIE9) {
- this.handleResize();
- }
- },
- // </if>
- onElementResize: function(element, size) {
- this.handleResize(size);
- },
- handleResize: function(size, instantly) {
- var me = this,
- el = me.element;
- if (!(el && (size = size || el.getSize()) && size.width && size.height)) {
- return;
- }
- me.resizeTimerId = Ext.undefer(me.resizeTimerId);
- if (!instantly && me.resizeDelay) {
- me.resizeTimerId = Ext.defer(me.handleResize, me.resizeDelay, me, [
- size,
- true
- ]);
- return;
- }
- me.size = size;
- me.resizeHandler(size);
- },
- updateMinValue: function(minValue) {
- var me = this;
- me.interpolator.setDomain(minValue, me.getMaxValue());
- if (!me.isConfiguring) {
- me.render();
- }
- },
- updateMaxValue: function(maxValue) {
- var me = this;
- me.interpolator.setDomain(me.getMinValue(), maxValue);
- if (!me.isConfiguring) {
- me.render();
- }
- },
- updateAngleOffset: function(angleOffset, oldAngleOffset) {
- var me = this,
- animation = me.getAnimation();
- me.fxAngleOffset = angleOffset;
- if (me.isConfiguring) {
- return;
- }
- if (animation.duration) {
- me.animate(oldAngleOffset, angleOffset, animation.duration, me.easings[animation.easing], function(angleOffset) {
- me.fxAngleOffset = angleOffset;
- me.render();
- });
- } else {
- me.render();
- }
- },
- //<debug>
- applyTrackStart: function(trackStart) {
- if (trackStart < 0 || trackStart >= 360) {
- Ext.raise("'trackStart' should be within [0, 360).");
- }
- return trackStart;
- },
- applyTrackLength: function(trackLength) {
- if (trackLength <= 0 || trackLength > 360) {
- Ext.raise("'trackLength' should be within (0, 360].");
- }
- return trackLength;
- },
- //</debug>
- updateTrackStart: function(trackStart) {
- var me = this;
- if (!me.isConfiguring) {
- me.render();
- }
- },
- updateTrackLength: function(trackLength) {
- var me = this;
- me.interpolator.setRange(0, trackLength);
- if (!me.isConfiguring) {
- me.render();
- }
- },
- applyPadding: function(padding) {
- var ratio;
- if (typeof padding === 'string') {
- ratio = parseFloat(padding) / 100;
- return function(x) {
- return x * ratio;
- };
- }
- return function() {
- return padding;
- };
- },
- updatePadding: function() {
- if (!this.isConfiguring) {
- this.render();
- }
- },
- applyValue: function(value) {
- var minValue = this.getMinValue(),
- maxValue = this.getMaxValue();
- return Math.min(Math.max(value, minValue), maxValue);
- },
- updateValue: function(value, oldValue) {
- var me = this,
- animation = me.getAnimation();
- me.fxValue = value;
- if (me.isConfiguring) {
- return;
- }
- me.writeText();
- if (animation.duration) {
- me.animate(oldValue, value, animation.duration, me.easings[animation.easing], function(value) {
- me.fxValue = value;
- me.render();
- });
- } else {
- me.render();
- }
- },
- applyTextTpl: function(textTpl) {
- if (textTpl && !textTpl.isTemplate) {
- textTpl = new Ext.XTemplate(textTpl);
- }
- return textTpl;
- },
- applyTextOffset: function(offset) {
- offset = offset || {};
- offset.dx = offset.dx || 0;
- offset.dy = offset.dy || 0;
- return offset;
- },
- updateTextTpl: function() {
- this.writeText();
- if (!this.isConfiguring) {
- this.centerText();
- }
- },
- // text will be centered on first size
- writeText: function(options) {
- var me = this,
- value = me.getValue(),
- minValue = me.getMinValue(),
- maxValue = me.getMaxValue(),
- delta = maxValue - minValue,
- textTpl = me.getTextTpl();
- textTpl.overwrite(me.textElement, {
- value: value,
- percent: (value - minValue) / delta * 100,
- minValue: minValue,
- maxValue: maxValue,
- delta: delta
- });
- },
- centerText: function(cx, cy, sectorRegion, innerRadius, outerRadius) {
- var textElement = this.textElement,
- textAlign = this.getTextAlign(),
- alignedRegion, textBox;
- if (Ext.Number.isEqual(innerRadius, 0, 0.1) || sectorRegion.isOutOfBound({
- x: cx,
- y: cy
- })) {
- alignedRegion = textElement.getRegion().alignTo({
- align: textAlign,
- // align text region's center to sector region's center
- target: sectorRegion
- });
- textElement.setLeft(alignedRegion.left);
- textElement.setTop(alignedRegion.top);
- } else {
- textBox = textElement.getBox();
- textElement.setLeft(cx - textBox.width / 2);
- textElement.setTop(cy - textBox.height / 2);
- }
- },
- camelCaseRe: /([a-z])([A-Z])/g,
- /**
- * @private
- */
- camelToHyphen: function(name) {
- return name.replace(this.camelCaseRe, '$1-$2').toLowerCase();
- },
- applyTrackStyle: function(trackStyle) {
- var me = this,
- trackGradient;
- trackStyle.innerRadius = me.getRadiusFn(trackStyle.innerRadius);
- trackStyle.outerRadius = me.getRadiusFn(trackStyle.outerRadius);
- if (Ext.isArray(trackStyle.fill)) {
- trackGradient = me.getTrackGradient();
- me.setGradientStops(trackGradient, trackStyle.fill);
- trackStyle.fill = 'url(#' + trackGradient.dom.getAttribute('id') + ')';
- }
- return trackStyle;
- },
- updateTrackStyle: function(trackStyle) {
- var me = this,
- trackArc = Ext.fly(me.getTrackArc()),
- name;
- for (name in trackStyle) {
- if (name in me.pathAttributes) {
- trackArc.setStyle(me.camelToHyphen(name), trackStyle[name]);
- } else {
- trackArc.setStyle(name, trackStyle[name]);
- }
- }
- },
- applyValueStyle: function(valueStyle) {
- var me = this,
- valueGradient;
- valueStyle.innerRadius = me.getRadiusFn(valueStyle.innerRadius);
- valueStyle.outerRadius = me.getRadiusFn(valueStyle.outerRadius);
- if (Ext.isArray(valueStyle.fill)) {
- valueGradient = me.getValueGradient();
- me.setGradientStops(valueGradient, valueStyle.fill);
- valueStyle.fill = 'url(#' + valueGradient.dom.getAttribute('id') + ')';
- }
- return valueStyle;
- },
- updateValueStyle: function(valueStyle) {
- var me = this,
- valueArc = Ext.fly(me.getValueArc()),
- name;
- for (name in valueStyle) {
- if (name in me.pathAttributes) {
- valueArc.setStyle(me.camelToHyphen(name), valueStyle[name]);
- } else {
- valueArc.setStyle(name, valueStyle[name]);
- }
- }
- },
- /**
- * @private
- */
- getRadiusFn: function(radius) {
- var result, pos, ratio,
- increment = 0;
- if (Ext.isNumber(radius)) {
- result = function() {
- return radius;
- };
- } else if (Ext.isString(radius)) {
- radius = radius.replace(/ /g, '');
- ratio = parseFloat(radius) / 100;
- pos = radius.search('%');
- // E.g. '100% - 4'
- if (pos < radius.length - 1) {
- increment = parseFloat(radius.substr(pos + 1));
- }
- result = function(radius) {
- return radius * ratio + increment;
- };
- result.ratio = ratio;
- }
- return result;
- },
- getSvg: function() {
- var me = this,
- svg = me.svg;
- if (!svg) {
- svg = me.svg = Ext.get(document.createElementNS(me.svgNS, 'svg'));
- me.bodyElement.append(svg);
- }
- return svg;
- },
- getTrackArc: function() {
- var me = this,
- trackArc = me.trackArc;
- if (!trackArc) {
- trackArc = me.trackArc = document.createElementNS(me.svgNS, 'path');
- me.getSvg().append(trackArc, true);
- // Note: Ext.dom.Element.addCls doesn't work on SVG elements,
- // as it simply assigns a class string to el.dom.className,
- // which in case of SVG is no simple string:
- // SVGAnimatedString {baseVal: "x-gauge-track", animVal: "x-gauge-track"}
- trackArc.setAttribute('class', Ext.baseCSSPrefix + 'gauge-track');
- }
- return trackArc;
- },
- getValueArc: function() {
- var me = this,
- valueArc = me.valueArc;
- me.getTrackArc();
- // make sure the track arc is created first for proper draw order
- if (!valueArc) {
- valueArc = me.valueArc = document.createElementNS(me.svgNS, 'path');
- me.getSvg().append(valueArc, true);
- valueArc.setAttribute('class', Ext.baseCSSPrefix + 'gauge-value');
- }
- return valueArc;
- },
- applyNeedle: function(needle, oldNeedle) {
- // Make sure the track and value elements have been already created,
- // so that the needle element renders on top.
- this.getValueArc();
- return Ext.Factory.gaugeNeedle.update(oldNeedle, needle, this, 'createNeedle', 'needleDefaults');
- },
- createNeedle: function(config) {
- return Ext.apply({
- gauge: this
- }, config);
- },
- getDefs: function() {
- var me = this,
- defs = me.defs;
- if (!defs) {
- defs = me.defs = Ext.get(document.createElementNS(me.svgNS, 'defs'));
- me.getSvg().appendChild(defs);
- }
- return defs;
- },
- /**
- * @private
- */
- setGradientSize: function(gradient, x1, y1, x2, y2) {
- gradient.setAttribute('x1', x1);
- gradient.setAttribute('y1', y1);
- gradient.setAttribute('x2', x2);
- gradient.setAttribute('y2', y2);
- },
- /**
- * @private
- */
- resizeGradients: function(size) {
- var me = this,
- trackGradient = me.getTrackGradient(),
- valueGradient = me.getValueGradient(),
- x1 = 0,
- y1 = size.height / 2,
- x2 = size.width,
- y2 = size.height / 2;
- me.setGradientSize(trackGradient.dom, x1, y1, x2, y2);
- me.setGradientSize(valueGradient.dom, x1, y1, x2, y2);
- },
- /**
- * @private
- */
- setGradientStops: function(gradient, stops) {
- var ln = stops.length,
- i, stopCfg, stopEl;
- while (gradient.firstChild) {
- gradient.removeChild(gradient.firstChild);
- }
- for (i = 0; i < ln; i++) {
- stopCfg = stops[i];
- stopEl = document.createElementNS(this.svgNS, 'stop');
- gradient.appendChild(stopEl);
- stopEl.setAttribute('offset', stopCfg.offset);
- stopEl.setAttribute('stop-color', stopCfg.color);
- if ('opacity' in stopCfg) {
- stopEl.setAttribute('stop-opacity', stopCfg.opacity);
- }
- }
- },
- getTrackGradient: function() {
- var me = this,
- trackGradient = me.trackGradient;
- if (!trackGradient) {
- trackGradient = me.trackGradient = Ext.get(document.createElementNS(me.svgNS, 'linearGradient'));
- // Using absolute values for x1, y1, x2, y2 attributes.
- trackGradient.dom.setAttribute('gradientUnits', 'userSpaceOnUse');
- me.getDefs().appendChild(trackGradient);
- Ext.get(trackGradient);
- }
- // assign unique ID
- return trackGradient;
- },
- getValueGradient: function() {
- var me = this,
- valueGradient = me.valueGradient;
- if (!valueGradient) {
- valueGradient = me.valueGradient = Ext.get(document.createElementNS(me.svgNS, 'linearGradient'));
- // Using absolute values for x1, y1, x2, y2 attributes.
- valueGradient.dom.setAttribute('gradientUnits', 'userSpaceOnUse');
- me.getDefs().appendChild(valueGradient);
- Ext.get(valueGradient);
- }
- // assign unique ID
- return valueGradient;
- },
- getArcPoint: function(centerX, centerY, radius, degrees) {
- var radians = degrees / 180 * Math.PI;
- return [
- centerX + radius * Math.cos(radians),
- centerY + radius * Math.sin(radians)
- ];
- },
- isCircle: function(startAngle, endAngle) {
- return Ext.Number.isEqual(Math.abs(endAngle - startAngle), 360, 0.001);
- },
- getArcPath: function(centerX, centerY, innerRadius, outerRadius, startAngle, endAngle, round) {
- var me = this,
- isCircle = me.isCircle(startAngle, endAngle),
- // It's not possible to draw a circle using arcs.
- endAngle = endAngle - 0.01,
- // eslint-disable-line no-redeclare
- innerStartPoint = me.getArcPoint(centerX, centerY, innerRadius, startAngle),
- innerEndPoint = me.getArcPoint(centerX, centerY, innerRadius, endAngle),
- outerStartPoint = me.getArcPoint(centerX, centerY, outerRadius, startAngle),
- outerEndPoint = me.getArcPoint(centerX, centerY, outerRadius, endAngle),
- large = endAngle - startAngle <= 180 ? 0 : 1,
- path = [
- 'M',
- innerStartPoint[0],
- innerStartPoint[1],
- 'A',
- innerRadius,
- innerRadius,
- 0,
- large,
- 1,
- innerEndPoint[0],
- innerEndPoint[1]
- ],
- capRadius = (outerRadius - innerRadius) / 2;
- if (isCircle) {
- path.push('M', outerEndPoint[0], outerEndPoint[1]);
- } else {
- if (round) {
- path.push('A', capRadius, capRadius, 0, 0, 0, outerEndPoint[0], outerEndPoint[1]);
- } else {
- path.push('L', outerEndPoint[0], outerEndPoint[1]);
- }
- }
- path.push('A', outerRadius, outerRadius, 0, large, 0, outerStartPoint[0], outerStartPoint[1]);
- if (round && !isCircle) {
- path.push('A', capRadius, capRadius, 0, 0, 0, innerStartPoint[0], innerStartPoint[1]);
- }
- path.push('Z');
- return path.join(' ');
- },
- resizeHandler: function(size) {
- var me = this,
- svg = me.getSvg();
- svg.setSize(size);
- me.resizeGradients(size);
- me.render();
- },
- /**
- * @private
- * Creates a linear interpolator function that itself has a few methods:
- * - `setDomain(from, to)`
- * - `setRange(from, to)`
- * - `getDomain` - returns the domain as a [from, to] array
- * - `getRange` - returns the range as a [from, to] array
- * @param {Boolean} [rangeCheck=false]
- * Whether to allow out of bounds values for domain and range.
- * @return {Function} The interpolator function:
- * `interpolator(domainValue, isInvert)`.
- * If the `isInvert` parameter is `true`, the start of domain will correspond
- * to the end of range. This is useful, for example, when you want to render
- * increasing domain values counter-clockwise instead of clockwise.
- */
- createInterpolator: function(rangeCheck) {
- var domainStart = 0,
- domainDelta = 1,
- rangeStart = 0,
- rangeEnd = 1,
- interpolator = function(x, invert) {
- var t = 0;
- if (domainDelta) {
- t = (x - domainStart) / domainDelta;
- if (rangeCheck) {
- t = Math.max(0, t);
- t = Math.min(1, t);
- }
- if (invert) {
- t = 1 - t;
- }
- }
- return (1 - t) * rangeStart + t * rangeEnd;
- };
- interpolator.setDomain = function(a, b) {
- domainStart = a;
- domainDelta = b - a;
- return this;
- };
- interpolator.setRange = function(a, b) {
- rangeStart = a;
- rangeEnd = b;
- return this;
- };
- interpolator.getDomain = function() {
- return [
- domainStart,
- domainStart + domainDelta
- ];
- };
- interpolator.getRange = function() {
- return [
- rangeStart,
- rangeEnd
- ];
- };
- return interpolator;
- },
- applyAnimation: function(animation) {
- if (true === animation) {
- animation = {};
- } else if (false === animation) {
- animation = {
- duration: 0
- };
- }
- if (!('duration' in animation)) {
- animation.duration = 1000;
- }
- if (!(animation.easing in this.easings)) {
- animation.easing = 'out';
- }
- return animation;
- },
- updateAnimation: function() {
- this.stopAnimation();
- },
- /**
- * @private
- * @param {Number} from
- * @param {Number} to
- * @param {Number} duration
- * @param {Function} easing
- * @param {Function} fn Function to execute on every frame of animation.
- * The function takes a single parameter - the value in the [from, to]
- * range, interpolated based on current time and easing function.
- * With certain easings, the value may overshoot the range slighly.
- * @param {Object} scope
- */
- animate: function(from, to, duration, easing, fn, scope) {
- var me = this,
- start = Ext.now(),
- interpolator = me.createInterpolator().setRange(from, to);
- function frame() {
- var now = Ext.AnimationQueue.frameStartTime,
- t = Math.min(now - start, duration) / duration,
- value = interpolator(easing(t));
- if (scope) {
- if (typeof fn === 'string') {
- scope[fn].call(scope, value);
- } else {
- fn.call(scope, value);
- }
- } else {
- fn(value);
- }
- if (t >= 1) {
- Ext.AnimationQueue.stop(frame, scope);
- me.fx = null;
- }
- }
- me.stopAnimation();
- Ext.AnimationQueue.start(frame, scope);
- me.fx = {
- frame: frame,
- scope: scope
- };
- },
- /**
- * Stops the current {@link #value} or {@link #angleOffset} animation.
- */
- stopAnimation: function() {
- var me = this;
- if (me.fx) {
- Ext.AnimationQueue.stop(me.fx.frame, me.fx.scope);
- me.fx = null;
- }
- },
- unitCircleExtrema: {
- 0: [
- 1,
- 0
- ],
- 90: [
- 0,
- 1
- ],
- 180: [
- -1,
- 0
- ],
- 270: [
- 0,
- -1
- ],
- 360: [
- 1,
- 0
- ],
- 450: [
- 0,
- 1
- ],
- 540: [
- -1,
- 0
- ],
- 630: [
- 0,
- -1
- ]
- },
- /**
- * @private
- */
- getUnitSectorExtrema: function(startAngle, lengthAngle) {
- var extrema = this.unitCircleExtrema,
- points = [],
- angle;
- for (angle in extrema) {
- if (angle > startAngle && angle < startAngle + lengthAngle) {
- points.push(extrema[angle]);
- }
- }
- return points;
- },
- /**
- * @private
- * Given a rect with a known width and height, find the maximum radius of the donut
- * sector that can fit into it, as well as the center point of such a sector.
- * The end and start angles of the sector are also known, as well as the relationship
- * between the inner and outer radii.
- */
- fitSectorInRect: function(width, height, startAngle, lengthAngle, ratio) {
- if (Ext.Number.isEqual(lengthAngle, 360, 0.001)) {
- return {
- cx: width / 2,
- cy: height / 2,
- radius: Math.min(width, height) / 2,
- region: new Ext.util.Region(0, width, height, 0)
- };
- }
- // eslint-disable-next-line vars-on-top
- var me = this,
- points, xx, yy, minX, maxX, minY, maxY,
- cache = me.fitSectorInRectCache,
- sameAngles = cache.startAngle === startAngle && cache.lengthAngle === lengthAngle;
- if (sameAngles) {
- minX = cache.minX;
- maxX = cache.maxX;
- minY = cache.minY;
- maxY = cache.maxY;
- } else {
- points = me.getUnitSectorExtrema(startAngle, lengthAngle).concat([
- // start angle outer radius point
- me.getArcPoint(0, 0, 1, startAngle),
- // start angle inner radius point
- me.getArcPoint(0, 0, ratio, startAngle),
- // end angle outer radius point
- me.getArcPoint(0, 0, 1, startAngle + lengthAngle),
- // end angle inner radius point
- me.getArcPoint(0, 0, ratio, startAngle + lengthAngle)
- ]);
- xx = points.map(function(point) {
- return point[0];
- });
- yy = points.map(function(point) {
- return point[1];
- });
- // The bounding box of a unit sector with the given properties.
- minX = Math.min.apply(null, xx);
- maxX = Math.max.apply(null, xx);
- minY = Math.min.apply(null, yy);
- maxY = Math.max.apply(null, yy);
- cache.startAngle = startAngle;
- cache.lengthAngle = lengthAngle;
- cache.minX = minX;
- cache.maxX = maxX;
- cache.minY = minY;
- cache.maxY = maxY;
- }
- // eslint-disable-next-line vars-on-top, one-var
- var sectorWidth = maxX - minX,
- sectorHeight = maxY - minY,
- scaleX = width / sectorWidth,
- scaleY = height / sectorHeight,
- scale = Math.min(scaleX, scaleY),
- // Region constructor takes: top, right, bottom, left.
- sectorRegion = new Ext.util.Region(minY * scale, maxX * scale, maxY * scale, minX * scale),
- rectRegion = new Ext.util.Region(0, width, height, 0),
- alignedRegion = sectorRegion.alignTo({
- align: 'c-c',
- // align sector region's center to rect region's center
- target: rectRegion
- }),
- dx = alignedRegion.left - minX * scale,
- dy = alignedRegion.top - minY * scale;
- return {
- cx: dx,
- cy: dy,
- radius: scale,
- region: alignedRegion
- };
- },
- /**
- * @private
- */
- fitSectorInPaddedRect: function(width, height, padding, startAngle, lengthAngle, ratio) {
- var result = this.fitSectorInRect(width - padding * 2, height - padding * 2, startAngle, lengthAngle, ratio);
- result.cx += padding;
- result.cy += padding;
- result.region.translateBy(padding, padding);
- return result;
- },
- /**
- * @private
- */
- normalizeAngle: function(angle) {
- return (angle % 360 + 360) % 360;
- },
- render: function() {
- if (!this.size) {
- return;
- }
- // eslint-disable-next-line vars-on-top
- var me = this,
- textOffset = me.getTextOffset(),
- trackArc = me.getTrackArc(),
- valueArc = me.getValueArc(),
- needle = me.getNeedle(),
- clockwise = me.getClockwise(),
- value = me.fxValue,
- angleOffset = me.fxAngleOffset,
- trackLength = me.getTrackLength(),
- width = me.size.width,
- height = me.size.height,
- paddingFn = me.getPadding(),
- padding = paddingFn(Math.min(width, height)),
- // in the range of [0, 360)
- trackStart = me.normalizeAngle(me.getTrackStart() + angleOffset),
- // in the range of (0, 720)
- trackEnd = trackStart + trackLength,
- valueLength = me.interpolator(value),
- trackStyle = me.getTrackStyle(),
- valueStyle = me.getValueStyle(),
- sector = me.fitSectorInPaddedRect(width, height, padding, trackStart, trackLength, trackStyle.innerRadius.ratio),
- cx = sector.cx,
- cy = sector.cy,
- radius = sector.radius,
- trackInnerRadius = Math.max(0, trackStyle.innerRadius(radius)),
- trackOuterRadius = Math.max(0, trackStyle.outerRadius(radius)),
- valueInnerRadius = Math.max(0, valueStyle.innerRadius(radius)),
- valueOuterRadius = Math.max(0, valueStyle.outerRadius(radius)),
- trackPath = me.getArcPath(cx, cy, trackInnerRadius, trackOuterRadius, trackStart, trackEnd, trackStyle.round),
- valuePath = me.getArcPath(cx, cy, valueInnerRadius, valueOuterRadius, clockwise ? trackStart : trackEnd - valueLength, clockwise ? trackStart + valueLength : trackEnd, valueStyle.round);
- me.centerText(cx + textOffset.dx, cy + textOffset.dy, sector.region, trackInnerRadius, trackOuterRadius);
- trackArc.setAttribute('d', trackPath);
- valueArc.setAttribute('d', valuePath);
- if (needle) {
- needle.setRadius(radius);
- needle.setTransform(cx, cy, -90 + trackStart + valueLength);
- }
- me.fireEvent('render', me);
- }
- });
- Ext.define('Ext.ux.gauge.needle.Arrow', {
- extend: 'Ext.ux.gauge.needle.Abstract',
- alias: 'gauge.needle.arrow',
- config: {
- path: function(ir, or) {
- 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" : '';
- }
- }
- });
- Ext.define('Ext.ux.gauge.needle.Diamond', {
- extend: 'Ext.ux.gauge.needle.Abstract',
- alias: 'gauge.needle.diamond',
- config: {
- path: function(ir, or) {
- return or - ir > 10 ? 'M0,' + ir + ' L-4,' + (ir + 5) + ' L0,' + or + ' L4,' + (ir + 5) + ' Z' : '';
- }
- }
- });
- Ext.define('Ext.ux.gauge.needle.Rectangle', {
- extend: 'Ext.ux.gauge.needle.Abstract',
- alias: 'gauge.needle.rectangle',
- config: {
- path: function(ir, or) {
- return or - ir > 10 ? "M-2," + ir + " L2," + ir + " L2," + or + " L-2," + or + " Z" : '';
- }
- }
- });
- Ext.define('Ext.ux.gauge.needle.Spike', {
- extend: 'Ext.ux.gauge.needle.Abstract',
- alias: 'gauge.needle.spike',
- config: {
- path: function(ir, or) {
- return or - ir > 10 ? "M0," + (ir + 5) + " L-4," + ir + " L0," + or + " L4," + ir + " Z" : '';
- }
- }
- });
- Ext.define('Ext.ux.gauge.needle.Wedge', {
- extend: 'Ext.ux.gauge.needle.Abstract',
- alias: 'gauge.needle.wedge',
- config: {
- path: function(ir, or) {
- return or - ir > 10 ? "M-4," + ir + " L0," + or + " L4," + ir + " Z" : '';
- }
- }
- });
- /**
- * A ratings picker based on `Ext.Gadget`.
- *
- * @example
- * Ext.create({
- * xtype: 'rating',
- * renderTo: Ext.getBody(),
- * listeners: {
- * change: function (picker, value) {
- * console.log('Rating ' + value);
- * }
- * }
- * });
- */
- Ext.define('Ext.ux.rating.Picker', {
- extend: 'Ext.Gadget',
- xtype: 'rating',
- focusable: true,
- /*
- * The "cachedConfig" block is basically the same as "config" except that these
- * values are applied specially to the first instance of the class. After processing
- * these configs, the resulting values are stored on the class `prototype` and the
- * template DOM element also reflects these default values.
- */
- cachedConfig: {
- /**
- * @cfg {String} [family]
- * The CSS `font-family` to use for displaying the `{@link #glyphs}`.
- */
- family: 'monospace',
- /**
- * @cfg {String/String[]/Number[]} [glyphs]
- * Either a string containing the two glyph characters, or an array of two strings
- * containing the individual glyph characters or an array of two numbers with the
- * character codes for the individual glyphs.
- *
- * For example:
- *
- * @example
- * Ext.create({
- * xtype: 'rating',
- * renderTo: Ext.getBody(),
- * glyphs: [ 9671, 9670 ], // '◇◆',
- * listeners: {
- * change: function (picker, value) {
- * console.log('Rating ' + value);
- * }
- * }
- * });
- */
- glyphs: '☆★',
- /**
- * @cfg {Number} [minimum=1]
- * The minimum allowed `{@link #value}` (rating).
- */
- minimum: 1,
- /**
- * @cfg {Number} [limit]
- * The maximum allowed `{@link #value}` (rating).
- */
- limit: 5,
- /**
- * @cfg {String/Object} [overStyle]
- * Optional styles to apply to the rating glyphs when `{@link #trackOver}` is
- * enabled.
- */
- overStyle: null,
- /**
- * @cfg {Number} [rounding=1]
- * The rounding to apply to values. Common choices are 0.5 (for half-steps) or
- * 0.25 (for quarter steps).
- */
- rounding: 1,
- /**
- * @cfg {String} [scale="125%"]
- * The CSS `font-size` to apply to the glyphs. This value defaults to 125% because
- * glyphs in the stock font tend to be too small. When using specially designed
- * "icon fonts" you may want to set this to 100%.
- */
- scale: '125%',
- /**
- * @cfg {String/Object} [selectedStyle]
- * Optional styles to apply to the rating value glyphs.
- */
- selectedStyle: null,
- /**
- * @cfg {Object/String/String[]/Ext.XTemplate/Function} tip
- * A template or a function that produces the tooltip text. The `Object`, `String`
- * and `String[]` forms are converted to an `Ext.XTemplate`. If a function is given,
- * it will be called with an object parameter and should return the tooltip text.
- * The object contains these properties:
- *
- * - component: The rating component requesting the tooltip.
- * - tracking: The current value under the mouse cursor.
- * - trackOver: The value of the `{@link #trackOver}` config.
- * - value: The current value.
- *
- * Templates can use these properties to generate the proper text.
- */
- tip: null,
- /**
- * @cfg {Boolean} [trackOver=true]
- * Determines if mouse movements should temporarily update the displayed value.
- * The actual `value` is only updated on `click` but this rather acts as the
- * "preview" of the value prior to click.
- */
- trackOver: true,
- /**
- * @cfg {Number} value
- * The rating value. This value is bounded by `minimum` and `limit` and is also
- * adjusted by the `rounding`.
- */
- value: null,
- //---------------------------------------------------------------------
- // Private configs
- /**
- * @cfg {String} tooltipText
- * The current tooltip text. This value is set into the DOM by the updater (hence
- * only when it changes). This is intended for use by the tip manager
- * (`{@link Ext.tip.QuickTipManager}`). Developers should never need to set this
- * config since it is handled by virtue of setting other configs (such as the
- * {@link #tooltip} or the {@link #value}.).
- * @private
- */
- tooltipText: null,
- /**
- * @cfg {Number} trackingValue
- * This config is used to when `trackOver` is `true` and represents the tracked
- * value. This config is maintained by our `mousemove` handler. This should not
- * need to be set directly by user code.
- * @private
- */
- trackingValue: null
- },
- config: {
- /**
- * @cfg {Boolean/Object} [animate=false]
- * Specifies an animation to use when changing the `{@link #value}`. When setting
- * this config, it is probably best to set `{@link #trackOver}` to `false`.
- */
- animate: null
- },
- // This object describes our element tree from the root.
- element: {
- cls: 'u' + Ext.baseCSSPrefix + 'rating-picker',
- // Since we are replacing the entire "element" tree, we have to assign this
- // "reference" as would our base class.
- reference: 'element',
- children: [
- {
- reference: 'innerEl',
- cls: 'u' + Ext.baseCSSPrefix + 'rating-picker-inner',
- listeners: {
- click: 'onClick',
- mousemove: 'onMouseMove',
- mouseenter: 'onMouseEnter',
- mouseleave: 'onMouseLeave'
- },
- children: [
- {
- reference: 'valueEl',
- cls: 'u' + Ext.baseCSSPrefix + 'rating-picker-value'
- },
- {
- reference: 'trackerEl',
- cls: 'u' + Ext.baseCSSPrefix + 'rating-picker-tracker'
- }
- ]
- }
- ]
- },
- // Tell the Binding system to default to our "value" config.
- defaultBindProperty: 'value',
- // Enable two-way data binding for the "value" config.
- twoWayBindable: 'value',
- overCls: 'u' + Ext.baseCSSPrefix + 'rating-picker-over',
- trackOverCls: 'u' + Ext.baseCSSPrefix + 'rating-picker-track-over',
- //-------------------------------------------------------------------------
- // Config Appliers
- applyGlyphs: function(value) {
- if (typeof value === 'string') {
- //<debug>
- if (value.length !== 2) {
- Ext.raise('Expected 2 characters for "glyphs" not "' + value + '".');
- }
- //</debug>
- value = [
- value.charAt(0),
- value.charAt(1)
- ];
- } else if (typeof value[0] === 'number') {
- value = [
- String.fromCharCode(value[0]),
- String.fromCharCode(value[1])
- ];
- }
- return value;
- },
- applyOverStyle: function(style) {
- this.trackerEl.applyStyles(style);
- },
- applySelectedStyle: function(style) {
- this.valueEl.applyStyles(style);
- },
- applyTip: function(tip) {
- if (tip && typeof tip !== 'function') {
- if (!tip.isTemplate) {
- tip = new Ext.XTemplate(tip);
- }
- tip = tip.apply.bind(tip);
- }
- return tip;
- },
- applyTrackingValue: function(value) {
- return this.applyValue(value);
- },
- // same rounding as normal value
- applyValue: function(v) {
- var rounding, limit, min;
- if (v !== null) {
- rounding = this.getRounding();
- limit = this.getLimit();
- min = this.getMinimum();
- v = Math.round(Math.round(v / rounding) * rounding * 1000) / 1000;
- v = (v < min) ? min : (v > limit ? limit : v);
- }
- return v;
- },
- //-------------------------------------------------------------------------
- // Event Handlers
- onClick: function(event) {
- var value = this.valueFromEvent(event);
- this.setValue(value);
- },
- onMouseEnter: function() {
- this.element.addCls(this.overCls);
- },
- onMouseLeave: function() {
- this.element.removeCls(this.overCls);
- },
- onMouseMove: function(event) {
- var value = this.valueFromEvent(event);
- this.setTrackingValue(value);
- },
- //-------------------------------------------------------------------------
- // Config Updaters
- updateFamily: function(family) {
- this.element.setStyle('fontFamily', "'" + family + "'");
- },
- updateGlyphs: function() {
- this.refreshGlyphs();
- },
- updateLimit: function() {
- this.refreshGlyphs();
- },
- updateScale: function(size) {
- this.element.setStyle('fontSize', size);
- },
- updateTip: function() {
- this.refreshTip();
- },
- updateTooltipText: function(text) {
- this.setTooltip(text);
- },
- // modern only (replaced by classic override)
- updateTrackingValue: function(value) {
- var me = this,
- trackerEl = me.trackerEl,
- newWidth = me.valueToPercent(value);
- trackerEl.setStyle('width', newWidth);
- me.refreshTip();
- },
- updateTrackOver: function(trackOver) {
- this.element.toggleCls(this.trackOverCls, trackOver);
- },
- updateValue: function(value, oldValue) {
- var me = this,
- animate = me.getAnimate(),
- valueEl = me.valueEl,
- newWidth = me.valueToPercent(value),
- column, record;
- if (me.isConfiguring || !animate) {
- valueEl.setStyle('width', newWidth);
- } else {
- valueEl.stopAnimation();
- valueEl.animate(Ext.merge({
- from: {
- width: me.valueToPercent(oldValue)
- },
- to: {
- width: newWidth
- }
- }, animate));
- }
- me.refreshTip();
- if (!me.isConfiguring) {
- // Since we are (re)configured many times as we are used in a grid cell, we
- // avoid firing the change event unless there are listeners.
- if (me.hasListeners.change) {
- me.fireEvent('change', me, value, oldValue);
- }
- column = me.getWidgetColumn && me.getWidgetColumn();
- record = column && me.getWidgetRecord && me.getWidgetRecord();
- if (record && column.dataIndex) {
- // When used in a widgetcolumn, we should update the backing field. The
- // linkages will be cleared as we are being recycled, so this will only
- // reach this line when we are properly attached to a record and the
- // change is coming from the user (or a call to setValue).
- record.set(column.dataIndex, value);
- }
- }
- },
- //-------------------------------------------------------------------------
- // Config System Optimizations
- //
- // These are to deal with configs that combine to determine what should be
- // rendered in the DOM. For example, "glyphs" and "limit" must both be known
- // to render the proper text nodes. The "tip" and "value" likewise are
- // used to update the tooltipText.
- //
- // To avoid multiple updates to the DOM (one for each config), we simply mark
- // the rendering as invalid and post-process these flags on the tail of any
- // bulk updates.
- afterCachedConfig: function() {
- // Now that we are done setting up the initial values we need to refresh the
- // DOM before we allow Ext.Widget's implementation to cloneNode on it.
- this.refresh();
- return this.callParent(arguments);
- },
- initConfig: function(instanceConfig) {
- this.isConfiguring = true;
- this.callParent([
- instanceConfig
- ]);
- // The firstInstance will already have refreshed the DOM (in afterCacheConfig)
- // but all instances beyond the first need to refresh if they have custom values
- // for one or more configs that affect the DOM (such as "glyphs" and "limit").
- this.refresh();
- },
- setConfig: function() {
- var me = this;
- // Since we could be updating multiple configs, save any updates that need
- // multiple values for afterwards.
- me.isReconfiguring = true;
- me.callParent(arguments);
- me.isReconfiguring = false;
- // Now that all new values are set, we can refresh the DOM.
- me.refresh();
- return me;
- },
- //-------------------------------------------------------------------------
- privates: {
- /**
- * This method returns the DOM text node into which glyphs are placed.
- * @param {HTMLElement} dom The DOM node parent of the text node.
- * @return {HTMLElement} The text node.
- * @private
- */
- getGlyphTextNode: function(dom) {
- var node = dom.lastChild;
- // We want all our text nodes to be at the end of the child list, most
- // especially the text node on the innerEl. That text node affects the
- // default left/right position of our absolutely positioned child divs
- // (trackerEl and valueEl).
- if (!node || node.nodeType !== 3) {
- node = dom.ownerDocument.createTextNode('');
- dom.appendChild(node);
- }
- return node;
- },
- getTooltipData: function() {
- var me = this;
- return {
- component: me,
- tracking: me.getTrackingValue(),
- trackOver: me.getTrackOver(),
- value: me.getValue()
- };
- },
- /**
- * Forcibly refreshes both glyph and tooltip rendering.
- * @private
- */
- refresh: function() {
- var me = this;
- if (me.invalidGlyphs) {
- me.refreshGlyphs(true);
- }
- if (me.invalidTip) {
- me.refreshTip(true);
- }
- },
- /**
- * Refreshes the glyph text rendering unless we are currently performing a
- * bulk config change (initConfig or setConfig).
- * @param {Boolean} now Pass `true` to force the refresh to happen now.
- * @private
- */
- refreshGlyphs: function(now) {
- var me = this,
- later = !now && (me.isConfiguring || me.isReconfiguring),
- el, glyphs, limit, on, off, trackerEl, valueEl;
- if (!later) {
- el = me.getGlyphTextNode(me.innerEl.dom);
- valueEl = me.getGlyphTextNode(me.valueEl.dom);
- trackerEl = me.getGlyphTextNode(me.trackerEl.dom);
- glyphs = me.getGlyphs();
- limit = me.getLimit();
- for (on = off = ''; limit--; ) {
- off += glyphs[0];
- on += glyphs[1];
- }
- el.nodeValue = off;
- valueEl.nodeValue = on;
- trackerEl.nodeValue = on;
- }
- me.invalidGlyphs = later;
- },
- /**
- * Refreshes the tooltip text rendering unless we are currently performing a
- * bulk config change (initConfig or setConfig).
- * @param {Boolean} now Pass `true` to force the refresh to happen now.
- * @private
- */
- refreshTip: function(now) {
- var me = this,
- later = !now && (me.isConfiguring || me.isReconfiguring),
- data, text, tooltip;
- if (!later) {
- tooltip = me.getTip();
- if (tooltip) {
- data = me.getTooltipData();
- text = tooltip(data);
- me.setTooltipText(text);
- }
- }
- me.invalidTip = later;
- },
- /**
- * Convert the coordinates of the given `Event` into a rating value.
- * @param {Ext.event.Event} event The event.
- * @return {Number} The rating based on the given event coordinates.
- * @private
- */
- valueFromEvent: function(event) {
- var me = this,
- el = me.innerEl,
- ex = event.getX(),
- rounding = me.getRounding(),
- cx = el.getX(),
- x = ex - cx,
- w = el.getWidth(),
- limit = me.getLimit(),
- v;
- if (me.getInherited().rtl) {
- x = w - x;
- }
- v = x / w * limit;
- // We have to round up here so that the area we are over is considered
- // the value.
- v = Math.ceil(v / rounding) * rounding;
- return v;
- },
- /**
- * Convert the given rating into a width percentage.
- * @param {Number} value The rating value to convert.
- * @return {String} The width percentage to represent the given value.
- * @private
- */
- valueToPercent: function(value) {
- value = (value / this.getLimit()) * 100;
- return value + '%';
- }
- }
- });
- /**
- * @class Ext.ux.rating.Picker
- */
- Ext.define('Ext.ux.overrides.rating.Picker', {
- override: 'Ext.ux.rating.Picker',
- //<debug>
- initConfig: function(config) {
- if (config && config.tooltip) {
- config.tip = config.tooltip;
- Ext.log.warn('[Ext.ux.rating.Picker] The "tooltip" config was replaced by "tip"');
- }
- this.callParent([
- config
- ]);
- },
- //</debug>
- updateTooltipText: function(text) {
- var innerEl = this.innerEl,
- QuickTips = Ext.tip && Ext.tip.QuickTipManager,
- tip = QuickTips && QuickTips.tip,
- target;
- if (QuickTips) {
- innerEl.dom.setAttribute('data-qtip', text);
- this.trackerEl.dom.setAttribute('data-qtip', text);
- // If the QuickTipManager is active over our widget, we need to update
- // the tooltip text directly.
- target = tip && tip.activeTarget;
- target = target && target.el;
- if (target && innerEl.contains(target)) {
- tip.update(text);
- }
- }
- }
- });
- /**
- * A DragDrop implementation specialized for use with BoxReorderer.
- */
- Ext.define('Ext.ux.dd.BoxContainerDD', {
- extend: 'Ext.dd.DD',
- /**
- * @method alignElWithMouse
- * @member Ext.dd.DD
- * @inheritdoc
- */
- alignElWithMouse: function(el, iPageX, iPageY) {
- var me = this,
- oCoord = me.getTargetCoord(iPageX, iPageY),
- x = oCoord.x,
- y = oCoord.y,
- fly = el.dom ? el : Ext.fly(el, '_dd'),
- aCoord, newLeft, newTop;
- if (!me.deltaSetXY) {
- aCoord = [
- Math.max(0, x),
- Math.max(0, y)
- ];
- fly.setXY(aCoord);
- newLeft = me.getLocalX(fly);
- newTop = fly.getLocalY();
- me.deltaSetXY = [
- newLeft - x,
- newTop - y
- ];
- } else {
- me.setLocalXY(fly, Math.max(0, x + me.deltaSetXY[0]), Math.max(0, y + me.deltaSetXY[1]));
- }
- me.cachePosition(x, y);
- me.autoScroll(x, y, el.offsetHeight, el.offsetWidth);
- return oCoord;
- }
- });
- /**
- * Base class from Ext.ux.TabReorderer.
- */
- Ext.define('Ext.ux.BoxReorderer', {
- extend: 'Ext.plugin.Abstract',
- alias: 'plugin.boxreorderer',
- requires: [
- 'Ext.ux.dd.BoxContainerDD'
- ],
- mixins: {
- observable: 'Ext.util.Observable'
- },
- /**
- * @cfg {String} itemSelector
- * A {@link Ext.DomQuery DomQuery} selector which identifies the encapsulating elements of child
- * Components which participate in reordering.
- */
- itemSelector: '.x-box-item',
- /**
- * @cfg {Mixed} animate
- * If truthy, child reordering is animated so that moved boxes slide smoothly into position.
- * If this option is numeric, it is used as the animation duration in milliseconds.
- */
- animate: 100,
- /**
- * @event StartDrag
- * Fires when dragging of a child Component begins.
- * @param {Ext.ux.BoxReorderer} this
- * @param {Ext.container.Container} container The owning Container
- * @param {Ext.Component} dragCmp The Component being dragged
- * @param {Number} idx The start index of the Component being dragged.
- */
- /**
- * @event Drag
- * Fires during dragging of a child Component.
- * @param {Ext.ux.BoxReorderer} this
- * @param {Ext.container.Container} container The owning Container
- * @param {Ext.Component} dragCmp The Component being dragged
- * @param {Number} startIdx The index position from which the Component was initially dragged.
- * @param {Number} idx The current closest index to which the Component would drop.
- */
- /**
- * @event ChangeIndex
- * Fires when dragging of a child Component causes its drop index to change.
- * @param {Ext.ux.BoxReorderer} this
- * @param {Ext.container.Container} container The owning Container
- * @param {Ext.Component} dragCmp The Component being dragged
- * @param {Number} startIdx The index position from which the Component was initially dragged.
- * @param {Number} idx The current closest index to which the Component would drop.
- */
- /**
- * @event Drop
- * Fires when a child Component is dropped at a new index position.
- * @param {Ext.ux.BoxReorderer} this
- * @param {Ext.container.Container} container The owning Container
- * @param {Ext.Component} dragCmp The Component being dropped
- * @param {Number} startIdx The index position from which the Component was initially dragged.
- * @param {Number} idx The index at which the Component is being dropped.
- */
- constructor: function() {
- this.callParent(arguments);
- this.mixins.observable.constructor.call(this);
- },
- init: function(container) {
- var me = this,
- layout = container.getLayout();
- me.container = container;
- // We must use LTR method names and properties.
- // The underlying Element APIs normalize them.
- me.names = layout._props[layout.type].names;
- // Set our animatePolicy to animate the start position (ie x for HBox, y for VBox)
- me.animatePolicy = {};
- me.animatePolicy[me.names.x] = true;
- // Initialize the DD on first layout, when the innerCt has been created.
- me.container.on({
- scope: me,
- boxready: me.onBoxReady,
- beforedestroy: me.onContainerDestroy
- });
- },
- /**
- * @private
- * Clear up on Container destroy
- */
- onContainerDestroy: function() {
- var dd = this.dd;
- if (dd) {
- dd.unreg();
- this.dd = null;
- }
- },
- onBoxReady: function() {
- var me = this,
- layout = me.container.getLayout(),
- names = me.names,
- dd;
- dd = me.dd = new Ext.ux.dd.BoxContainerDD(layout.innerCt, me.container.id + '-reorderer');
- Ext.apply(dd, {
- animate: me.animate,
- reorderer: me,
- container: me.container,
- getDragCmp: me.getDragCmp,
- clickValidator: Ext.Function.createInterceptor(dd.clickValidator, me.clickValidator, me, false),
- onMouseDown: me.onMouseDown,
- startDrag: me.startDrag,
- onDrag: me.onDrag,
- endDrag: me.endDrag,
- getNewIndex: me.getNewIndex,
- doSwap: me.doSwap,
- findReorderable: me.findReorderable,
- names: names
- });
- // Decide which dimension we are measuring, and which measurement metric defines
- // the *start* of the box depending upon orientation.
- dd.dim = names.width;
- dd.startAttr = names.beforeX;
- dd.endAttr = names.afterX;
- },
- getDragCmp: function(e) {
- return this.container.getChildByElement(e.getTarget(this.itemSelector, 10));
- },
- // check if the clicked component is reorderable
- clickValidator: function(e) {
- var cmp = this.getDragCmp(e);
- // If cmp is null, this expression MUST be coerced to boolean so that
- // createInterceptor is able to test it against false
- return !!(cmp && cmp.reorderable !== false);
- },
- onMouseDown: function(e) {
- var me = this,
- container = me.container,
- containerBox, cmpEl, cmpBox;
- // Ascertain which child Component is being mousedowned
- me.dragCmp = me.getDragCmp(e);
- if (me.dragCmp) {
- cmpEl = me.dragCmp.getEl();
- me.startIndex = me.curIndex = container.items.indexOf(me.dragCmp);
- // Start position of dragged Component
- cmpBox = cmpEl.getBox();
- // Last tracked start position
- me.lastPos = cmpBox[me.startAttr];
- // Calculate constraints depending upon orientation
- // Calculate offset from mouse to dragEl position
- containerBox = container.el.getBox();
- if (me.dim === 'width') {
- me.minX = containerBox.left;
- me.maxX = containerBox.right - cmpBox.width;
- me.minY = me.maxY = cmpBox.top;
- me.deltaX = e.getX() - cmpBox.left;
- } else {
- me.minY = containerBox.top;
- me.maxY = containerBox.bottom - cmpBox.height;
- me.minX = me.maxX = cmpBox.left;
- me.deltaY = e.getY() - cmpBox.top;
- }
- me.constrainY = me.constrainX = true;
- }
- },
- startDrag: function() {
- var me = this,
- dragCmp = me.dragCmp,
- targetEl, dom, left, top, scrollable;
- if (dragCmp) {
- // For the entire duration of dragging the *Element*, defeat any positioning
- // and animation of the dragged *Component*
- scrollable = me.container.getScrollable();
- if (scrollable) {
- // TODO remove this workaround
- scrollable.scrollBy(-1).then(function() {
- scrollable.scrollBy(1);
- });
- }
- dragCmp.setPosition = Ext.emptyFn;
- dragCmp.animate = false;
- // Animate the BoxLayout just for the duration of the drag operation.
- if (me.animate) {
- me.container.getLayout().animatePolicy = me.reorderer.animatePolicy;
- }
- // We drag the Component element
- me.dragElId = dragCmp.getEl().id;
- me.reorderer.fireEvent('StartDrag', me, me.container, dragCmp, me.curIndex);
- // Suspend events, and set the disabled flag so that the mousedown and mouseup events
- // that are going to take place do not cause any other UI interaction.
- dragCmp.suspendEvents();
- dragCmp.disabled = true;
- dragCmp.el.setStyle('zIndex', 100);
- // add a spacer to the tab container so it doesn't shrink while we're dragging a tab
- if (!dragCmp.nextSibling()) {
- targetEl = me.container.layout.targetEl;
- dom = targetEl.dom;
- left = dom.scrollWidth - 1;
- top = dom.scrollHeight - 1;
- me.spacerEl = Ext.dom.Helper.append(targetEl, {
- tag: 'div',
- style: 'width: 1px;' + 'height: 1px;' + 'position: absolute;' + 'left: ' + left + 'px;' + 'top: ' + top + 'px;"'
- });
- }
- } else {
- me.dragElId = null;
- }
- },
- /**
- * @private
- * Find next or previous reorderable component index.
- * @param {Number} newIndex The initial drop index.
- * @return {Number} The index of the reorderable component.
- */
- findReorderable: function(newIndex) {
- var me = this,
- items = me.container.items,
- newItem;
- if (items.getAt(newIndex).reorderable === false) {
- newItem = items.getAt(newIndex);
- if (newIndex > me.startIndex) {
- while (newItem && newItem.reorderable === false) {
- newIndex++;
- newItem = items.getAt(newIndex);
- }
- } else {
- while (newItem && newItem.reorderable === false) {
- newIndex--;
- newItem = items.getAt(newIndex);
- }
- }
- }
- newIndex = Math.min(Math.max(newIndex, 0), items.getCount() - 1);
- if (items.getAt(newIndex).reorderable === false) {
- return -1;
- }
- return newIndex;
- },
- /**
- * @private
- * Swap 2 components.
- * @param {Number} newIndex The initial drop index.
- */
- doSwap: function(newIndex) {
- var me = this,
- items = me.container.items,
- container = me.container,
- orig, dest, tmpIndex;
- newIndex = me.findReorderable(newIndex);
- if (newIndex === -1 || newIndex === me.curIndex) {
- return;
- }
- me.reorderer.fireEvent('ChangeIndex', me, container, me.dragCmp, me.startIndex, newIndex);
- orig = items.getAt(me.curIndex);
- dest = items.getAt(newIndex);
- items.remove(orig);
- tmpIndex = Math.min(Math.max(newIndex, 0), items.getCount() - 1);
- items.insert(tmpIndex, orig);
- items.remove(dest);
- items.insert(me.curIndex, dest);
- // Make the Box Container the topmost layout participant during the layout.
- container.updateLayout({
- isRoot: true
- });
- me.curIndex = newIndex;
- },
- onDrag: function(e) {
- var me = this,
- newIndex;
- newIndex = me.getNewIndex(e.getPoint());
- if ((newIndex !== undefined)) {
- me.reorderer.fireEvent('Drag', me, me.container, me.dragCmp, me.startIndex, me.curIndex);
- me.doSwap(newIndex);
- }
- },
- endDrag: function(e) {
- var me = this,
- dragCmp = me.dragCmp,
- container = me.container,
- layout = container.getLayout(),
- temp;
- if (e) {
- e.stopEvent();
- }
- if (dragCmp) {
- delete me.dragElId;
- // Reinstate the Component's positioning method after mouseup,
- // and allow the layout system to animate it.
- delete dragCmp.setPosition;
- dragCmp.animate = true;
- // Ensure the lastBox is correct for the animation system to restore
- // to when it creates the "from" animation frame
- dragCmp.lastBox[me.names.x] = dragCmp.getPosition(true)[me.names.widthIndex];
- // Make the Box Container the topmost layout participant during the layout.
- container.updateLayout({
- isRoot: true
- });
- // Attempt to hook into the afteranimate event of the drag Component to call the cleanup
- temp = Ext.fx.Manager.getFxQueue(dragCmp.el.id)[0];
- if (temp) {
- temp.on({
- afteranimate: me.reorderer.afterBoxReflow,
- scope: me
- });
- } else // If not animated, clean up after the mouseup has happened so that
- // we don't click the thing being dragged
- {
- Ext.asap(me.reorderer.afterBoxReflow, me);
- }
- if (me.animate) {
- delete layout.animatePolicy;
- }
- me.reorderer.fireEvent('drop', me, container, dragCmp, me.startIndex, me.curIndex);
- }
- },
- /**
- * @private
- * Called after the boxes have been reflowed after the drop.
- * Re-enabled the dragged Component.
- */
- afterBoxReflow: function() {
- var me = this,
- spacerEl = Ext.fly(me.spacerEl),
- dragCmp = me.dragCmp;
- dragCmp.el.setStyle('zIndex', '');
- dragCmp.disabled = false;
- dragCmp.resumeEvents();
- // remove the spacer that was added when the drag was started
- if (spacerEl) {
- spacerEl.remove();
- me.spacerEl = null;
- }
- },
- /**
- * @private
- * Calculate drop index based upon the dragEl's position.
- */
- getNewIndex: function(pointerPos) {
- var me = this,
- dragEl = me.getDragEl(),
- dragBox = Ext.fly(dragEl).getBox(),
- targetEl, targetBox, targetMidpoint,
- i = 0,
- it = me.container.items.items,
- ln = it.length,
- lastPos = me.lastPos;
- me.lastPos = dragBox[me.startAttr];
- for (; i < ln; i++) {
- targetEl = it[i].getEl();
- // Only look for a drop point if this found item is an item according to our selector
- if (targetEl.dom !== dragEl && targetEl.is(me.reorderer.itemSelector)) {
- targetBox = targetEl.getBox();
- targetMidpoint = targetBox[me.startAttr] + (targetBox[me.dim] >> 1);
- if (i < me.curIndex) {
- if ((dragBox[me.startAttr] < lastPos) && (dragBox[me.startAttr] < (targetMidpoint - 5))) {
- return i;
- }
- } else if (i > me.curIndex) {
- if ((dragBox[me.startAttr] > lastPos) && (dragBox[me.endAttr] > (targetMidpoint + 5))) {
- return i;
- }
- }
- }
- }
- }
- });
- /**
- * This plugin can enable a cell to cell drag and drop operation within the same grid view.
- *
- * Note that the plugin must be added to the grid view, not to the grid panel. For example,
- * using {@link Ext.panel.Table viewConfig}:
- *
- * viewConfig: {
- * plugins: {
- * celldragdrop: {
- * // Remove text from source cell and replace with value of emptyText.
- * applyEmptyText: true,
- *
- * //emptyText: Ext.String.htmlEncode('<<foo>>'),
- *
- * // Will only allow drops of the same type.
- * enforceType: true
- * }
- * }
- * }
- */
- Ext.define('Ext.ux.CellDragDrop', {
- extend: 'Ext.plugin.Abstract',
- alias: 'plugin.celldragdrop',
- uses: [
- 'Ext.view.DragZone'
- ],
- /**
- * @cfg {Boolean} enforceType
- * Set to `true` to only allow drops of the same type.
- *
- * Defaults to `false`.
- */
- enforceType: false,
- /**
- * @cfg {Boolean} applyEmptyText
- * If `true`, then use the value of {@link #emptyText} to replace the drag record's value
- * after a node drop. Note that, if dropped on a cell of a different type, it will convert
- * the default text according to its own conversion rules.
- *
- * Defaults to `false`.
- */
- applyEmptyText: false,
- /**
- * @cfg {String} emptyText
- * If {@link #applyEmptyText} is `true`, then this value as the drag record's value after
- * a node drop.
- *
- * Defaults to an empty string.
- */
- emptyText: '',
- /**
- * @cfg {String} dropBackgroundColor
- * The default background color for when a drop is allowed.
- *
- * Defaults to green.
- */
- dropBackgroundColor: 'green',
- /**
- * @cfg {String} noDropBackgroundColor
- * The default background color for when a drop is not allowed.
- *
- * Defaults to red.
- */
- noDropBackgroundColor: 'red',
- /**
- * @cfg {String} dragText
- * The text to show while dragging.
- *
- * Two placeholders can be used in the text:
- *
- * - `{0}` The number of selected items.
- * - `{1}` 's' when more than 1 items (only useful for English).
- * @locale
- */
- dragText: '{0} selected row{1}',
- /**
- * @cfg {String} ddGroup
- * A named drag drop group to which this object belongs. If a group is specified, then both
- * the DragZones and DropZone used by this plugin will only interact with other drag drop
- * objects in the same group.
- */
- ddGroup: "GridDD",
- /**
- * @cfg {Boolean} enableDrop
- * Set to `false` to disallow the View from accepting drop gestures.
- */
- enableDrop: true,
- /**
- * @cfg {Boolean} enableDrag
- * Set to `false` to disallow dragging items from the View.
- */
- enableDrag: true,
- /**
- * @cfg {Object/Boolean} containerScroll
- * True to register this container with the Scrollmanager for auto scrolling during drag
- * operations. A {@link Ext.dd.ScrollManager} configuration may also be passed.
- */
- containerScroll: false,
- init: function(view) {
- var me = this;
- view.on('render', me.onViewRender, me, {
- single: true
- });
- },
- destroy: function() {
- var me = this;
- me.dragZone = me.dropZone = Ext.destroy(me.dragZone, me.dropZone);
- me.callParent();
- },
- enable: function() {
- var me = this;
- if (me.dragZone) {
- me.dragZone.unlock();
- }
- if (me.dropZone) {
- me.dropZone.unlock();
- }
- me.callParent();
- },
- disable: function() {
- var me = this;
- if (me.dragZone) {
- me.dragZone.lock();
- }
- if (me.dropZone) {
- me.dropZone.lock();
- }
- me.callParent();
- },
- onViewRender: function(view) {
- var me = this,
- scrollEl;
- if (me.enableDrag) {
- if (me.containerScroll) {
- scrollEl = view.getEl();
- }
- me.dragZone = new Ext.view.DragZone({
- view: view,
- ddGroup: me.dragGroup || me.ddGroup,
- dragText: me.dragText,
- containerScroll: me.containerScroll,
- scrollEl: scrollEl,
- getDragData: function(e) {
- var view = this.view,
- item = e.getTarget(view.getItemSelector()),
- record = view.getRecord(item),
- cell = e.getTarget(view.getCellSelector()),
- dragEl, header;
- if (item) {
- dragEl = document.createElement('div');
- dragEl.className = 'x-form-text';
- dragEl.appendChild(document.createTextNode(cell.textContent || cell.innerText));
- header = view.getHeaderByCell(cell);
- return {
- event: new Ext.EventObjectImpl(e),
- ddel: dragEl,
- item: e.target,
- columnName: header.dataIndex,
- record: record
- };
- }
- },
- onInitDrag: function(x, y) {
- var self = this,
- data = self.dragData,
- view = self.view,
- selectionModel = view.getSelectionModel(),
- record = data.record,
- el = data.ddel;
- // Update the selection to match what would have been selected if the user had
- // done a full click on the target node rather than starting a drag from it.
- if (!selectionModel.isSelected(record)) {
- selectionModel.select(record, true);
- }
- Ext.fly(self.ddel).update(el.textContent || el.innerText);
- self.proxy.update(self.ddel);
- self.onStartDrag(x, y);
- return true;
- }
- });
- }
- if (me.enableDrop) {
- me.dropZone = new Ext.dd.DropZone(view.el, {
- view: view,
- ddGroup: me.dropGroup || me.ddGroup,
- containerScroll: true,
- getTargetFromEvent: function(e) {
- var self = this,
- view = self.view,
- cell = e.getTarget(view.cellSelector),
- row, header;
- // Ascertain whether the mousemove is within a grid cell.
- if (cell) {
- row = view.findItemByChild(cell);
- header = view.getHeaderByCell(cell);
- if (row && header) {
- return {
- node: cell,
- record: view.getRecord(row),
- columnName: header.dataIndex
- };
- }
- }
- },
- // On Node enter, see if it is valid for us to drop the field on that type of column
- onNodeEnter: function(target, dd, e, dragData) {
- var self = this,
- destType, sourceType;
- destType = target.record.getField(target.columnName).type.toUpperCase();
- sourceType = dragData.record.getField(dragData.columnName).type.toUpperCase();
- delete self.dropOK;
- // Return if no target node or if over the same cell as the source of the drag.
- if (!target || target.node === dragData.item.parentNode) {
- return;
- }
- // Check whether the data type of the column being dropped on accepts the
- // dragged field type. If so, set dropOK flag, and highlight the target node.
- if (me.enforceType && destType !== sourceType) {
- self.dropOK = false;
- if (me.noDropCls) {
- Ext.fly(target.node).addCls(me.noDropCls);
- } else {
- Ext.fly(target.node).applyStyles({
- backgroundColor: me.noDropBackgroundColor
- });
- }
- return false;
- }
- self.dropOK = true;
- if (me.dropCls) {
- Ext.fly(target.node).addCls(me.dropCls);
- } else {
- Ext.fly(target.node).applyStyles({
- backgroundColor: me.dropBackgroundColor
- });
- }
- },
- // Return the class name to add to the drag proxy. This provides a visual indication
- // of drop allowed or not allowed.
- onNodeOver: function(target, dd, e, dragData) {
- return this.dropOK ? this.dropAllowed : this.dropNotAllowed;
- },
- // Highlight the target node.
- onNodeOut: function(target, dd, e, dragData) {
- var cls = this.dropOK ? me.dropCls : me.noDropCls;
- if (cls) {
- Ext.fly(target.node).removeCls(cls);
- } else {
- Ext.fly(target.node).applyStyles({
- backgroundColor: ''
- });
- }
- },
- // Process the drop event if we have previously ascertained that a drop is OK.
- onNodeDrop: function(target, dd, e, dragData) {
- if (this.dropOK) {
- target.record.set(target.columnName, dragData.record.get(dragData.columnName));
- if (me.applyEmptyText) {
- dragData.record.set(dragData.columnName, me.emptyText);
- }
- return true;
- }
- },
- onCellDrop: Ext.emptyFn
- });
- }
- }
- });
- /**
- * @class Ext.ux.DataTip
- * @extends Ext.tip.ToolTip
- * This plugin implements automatic tooltip generation for an arbitrary number of child nodes
- * *within* a Component.
- *
- * This plugin is applied to a high level Component, which contains repeating elements,
- * and depending on the host Component type, it automatically selects
- * a {@link Ext.ToolTip#delegate delegate} so that it appears when the mouse enters a sub-element.
- *
- * When applied to a GridPanel, this ToolTip appears when over a row, and the Record's data
- * is applied using this object's {@link #tpl} template.
- *
- * When applied to a DataView, this ToolTip appears when over a view node, and the Record's data
- * is applied using this object's {@link #tpl} template.
- *
- * When applied to a TreePanel, this ToolTip appears when over a tree node, and the Node's
- * {@link Ext.data.Model} record data is applied using this object's {@link #tpl} template.
- *
- * When applied to a FormPanel, this ToolTip appears when over a Field, and the Field's `tooltip`
- * property is used is applied using this object's {@link #tpl} template, or if it is a string,
- * used as HTML content. If there is no `tooltip` property, the field itself is used
- * as the template's data object.
- *
- * If more complex logic is needed to determine content, then the {@link #beforeshow} event
- * may be used. This class also publishes a **`beforeshowtip`** event through its host Component.
- * The *host Component* fires the **`beforeshowtip`** event.
- */
- Ext.define('Ext.ux.DataTip', function(DataTip) {
- // Target the body (if the host is a Panel), or, if there is no body, the main Element.
- function onHostRender() {
- var e = this.isXType('panel') ? this.body : this.el;
- if (this.dataTip.renderToTarget) {
- this.dataTip.render(e);
- }
- this.dataTip.setTarget(e);
- }
- function updateTip(tip, data) {
- if (tip.rendered) {
- if (tip.host.fireEvent('beforeshowtip', tip.eventHost, tip, data) === false) {
- return false;
- }
- tip.update(data);
- } else {
- if (Ext.isString(data)) {
- tip.html = data;
- } else {
- tip.data = data;
- }
- }
- }
- function beforeViewTipShow(tip) {
- var rec = this.view.getRecord(tip.triggerElement),
- data;
- if (rec) {
- data = tip.initialConfig.data ? Ext.apply(tip.initialConfig.data, rec.data) : rec.data;
- return updateTip(tip, data);
- } else {
- return false;
- }
- }
- function beforeFormTipShow(tip) {
- var field = Ext.getCmp(tip.triggerElement.id);
- if (field && (field.tooltip || tip.tpl)) {
- return updateTip(tip, field.tooltip || field);
- } else {
- return false;
- }
- }
- return {
- extend: 'Ext.tip.ToolTip',
- mixins: {
- plugin: 'Ext.plugin.Abstract'
- },
- alias: 'plugin.datatip',
- lockableScope: 'both',
- constructor: function(config) {
- var me = this;
- me.callParent([
- config
- ]);
- me.mixins.plugin.constructor.call(me, config);
- },
- init: function(host) {
- var me = this;
- me.mixins.plugin.init.call(me, host);
- host.dataTip = me;
- me.host = host;
- if (host.isXType('tablepanel')) {
- me.view = host.getView();
- if (host.ownerLockable) {
- me.host = host.ownerLockable;
- }
- me.delegate = me.delegate || me.view.rowSelector;
- me.on('beforeshow', beforeViewTipShow);
- } else if (host.isXType('dataview')) {
- me.view = me.host;
- me.delegate = me.delegate || host.itemSelector;
- me.on('beforeshow', beforeViewTipShow);
- } else if (host.isXType('form')) {
- me.delegate = '.' + Ext.form.Labelable.prototype.formItemCls;
- me.on('beforeshow', beforeFormTipShow);
- } else if (host.isXType('combobox')) {
- me.view = host.getPicker();
- me.delegate = me.delegate || me.view.getItemSelector();
- me.on('beforeshow', beforeViewTipShow);
- }
- if (host.rendered) {
- onHostRender.call(host);
- } else {
- host.onRender = Ext.Function.createSequence(host.onRender, onHostRender);
- }
- }
- };
- });
- /**
- * Transition plugin for DataViews
- */
- Ext.define('Ext.ux.DataView.Animated', {
- alias: 'plugin.ux-animated-dataview',
- /**
- * @property defaults
- * @type Object
- * Default configuration options for all DataViewTransition instances
- */
- defaults: {
- duration: 750,
- idProperty: 'id'
- },
- /**
- * Creates the plugin instance, applies defaults
- * @constructor
- * @param {Object} config Optional config object
- */
- constructor: function(config) {
- Ext.apply(this, config || {}, this.defaults);
- },
- /**
- * Initializes the transition plugin. Overrides the dataview's default refresh function
- * @param {Ext.view.View} dataview The dataview
- */
- init: function(dataview) {
- var me = this,
- store = dataview.store,
- items = dataview.all,
- task = {
- interval: 20
- },
- duration = me.duration;
- /**
- * @property dataview
- * @type Ext.view.View
- * Reference to the DataView this instance is bound to
- */
- me.dataview = dataview;
- dataview.blockRefresh = true;
- dataview.updateIndexes = Ext.Function.createSequence(dataview.updateIndexes, function() {
- this.getTargetEl().select(this.itemSelector).each(function(element, composite, index) {
- element.dom.id = Ext.util.Format.format("{0}-{1}", dataview.id, store.getAt(index).internalId);
- }, this);
- }, dataview);
- /**
- * @property dataviewID
- * @type String
- * The string ID of the DataView component. This is used internally when animating
- * child objects
- */
- me.dataviewID = dataview.id;
- /**
- * @property cachedStoreData
- * @type Object
- * A cache of existing store data, keyed by id. This is used to determine
- * whether any items were added or removed from the store on data change
- */
- me.cachedStoreData = {};
- // catch the store data with the snapshot immediately
- me.cacheStoreData(store.data || store.snapshot);
- dataview.on('resize', function() {
- var store = dataview.store;
- if (store.getCount() > 0) {}
- }, // reDraw.call(this, store);
- this);
- // Buffer listenher so that rapid calls, for example a filter followed by a sort
- // Only produce one redraw.
- dataview.store.on({
- datachanged: reDraw,
- scope: this,
- buffer: 50
- });
- function reDraw() {
- var parentEl = dataview.getTargetEl(),
- parentElY = parentEl.getY(),
- parentElPaddingTop = parentEl.getPadding('t'),
- added = me.getAdded(store),
- removed = me.getRemoved(store),
- remaining = me.getRemaining(store),
- itemArray, i, id,
- itemFly = new Ext.dom.Fly(),
- rtl = me.dataview.getInherited().rtl,
- oldPos, newPos,
- styleSide = rtl ? 'right' : 'left',
- newStyle = {},
- oldPositions, newPositions, doAnimate;
- // Not yet rendered
- if (!parentEl) {
- return;
- }
- // Collect nodes that will be removed in the forthcoming refresh so
- // that we can put them back in order to fade them out
- Ext.iterate(removed, function(recId, item) {
- id = me.dataviewID + '-' + recId;
- // Stop any animations for removed items and ensure th.
- Ext.fx.Manager.stopAnimation(id);
- item.dom = Ext.getDom(id);
- if (!item.dom) {
- delete removed[recId];
- }
- });
- me.cacheStoreData(store);
- // stores the current top and left values for each element (discovered below)
- oldPositions = {};
- newPositions = {};
- // Find current positions of elements which are to remain after the refresh.
- Ext.iterate(remaining, function(id, item) {
- if (itemFly.attach(Ext.getDom(me.dataviewID + '-' + id))) {
- oldPos = oldPositions[id] = {
- top: itemFly.getY() - parentElY - itemFly.getMargin('t') - parentElPaddingTop
- };
- oldPos[styleSide] = me.getItemX(itemFly);
- } else {
- delete remaining[id];
- }
- });
- // The view MUST refresh, creating items in the natural flow, and collecting the items
- // so that its item collection is consistent.
- dataview.refresh();
- // Replace removed nodes so that they can be faded out, THEN removed
- Ext.iterate(removed, function(id, item) {
- parentEl.dom.appendChild(item.dom);
- itemFly.attach(item.dom).animate({
- duration: duration,
- opacity: 0,
- callback: function(anim) {
- var el = Ext.get(anim.target.id);
- if (el) {
- el.destroy();
- }
- }
- });
- delete item.dom;
- });
- // We have taken care of any removals.
- // If the store is empty, we are done.
- if (!store.getCount()) {
- return;
- }
- // Collect the correct new positions after the refresh
- itemArray = items.slice();
- // Reverse order so that moving to absolute position does not affect the position of
- // the next one we're looking at.
- for (i = itemArray.length - 1; i >= 0; i--) {
- id = store.getAt(i).internalId;
- itemFly.attach(itemArray[i]);
- newPositions[id] = {
- dom: itemFly.dom,
- top: itemFly.getY() - parentElY - itemFly.getMargin('t') - parentElPaddingTop
- };
- newPositions[id][styleSide] = me.getItemX(itemFly);
- // We're going to absolutely position each item.
- // If it is a "remaining" one from last refesh, shunt it back to
- // its old position from where it will be animated.
- newPos = oldPositions[id] || newPositions[id];
- // set absolute positioning on all DataView items. We need to set position, left and
- // top at the same time to avoid any flickering
- newStyle.position = 'absolute';
- newStyle.top = newPos.top + "px";
- newStyle[styleSide] = newPos.left + "px";
- itemFly.applyStyles(newStyle);
- }
- // This is the function which moves remaining items to their new position
- doAnimate = function() {
- var elapsed = new Date() - task.taskStartTime,
- fraction = elapsed / duration,
- oldPos, newPos, oldTop, newTop, oldLeft, newLeft, diffTop, diffLeft, midTop, midLeft;
- if (fraction >= 1) {
- // At end, return all items to natural flow.
- newStyle.position = newStyle.top = newStyle[styleSide] = '';
- for (id in newPositions) {
- itemFly.attach(newPositions[id].dom).applyStyles(newStyle);
- }
- Ext.TaskManager.stop(task);
- } else {
- // In frame, move each "remaining" item according to time elapsed
- for (id in remaining) {
- oldPos = oldPositions[id];
- newPos = newPositions[id];
- oldTop = oldPos.top;
- newTop = newPos.top;
- oldLeft = oldPos[styleSide];
- newLeft = newPos[styleSide];
- diffTop = fraction * Math.abs(oldTop - newTop);
- diffLeft = fraction * Math.abs(oldLeft - newLeft);
- midTop = oldTop > newTop ? oldTop - diffTop : oldTop + diffTop;
- midLeft = oldLeft > newLeft ? oldLeft - diffLeft : oldLeft + diffLeft;
- newStyle.top = midTop + "px";
- newStyle[styleSide] = midLeft + "px";
- itemFly.attach(newPos.dom).applyStyles(newStyle);
- }
- }
- };
- // Fade in new items
- Ext.iterate(added, function(id, item) {
- if (itemFly.attach(Ext.getDom(me.dataviewID + '-' + id))) {
- itemFly.setOpacity(0);
- itemFly.animate({
- duration: duration,
- opacity: 1
- });
- }
- });
- // Stop any previous animations
- Ext.TaskManager.stop(task);
- task.run = doAnimate;
- Ext.TaskManager.start(task);
- me.cacheStoreData(store);
- }
- },
- getItemX: function(el) {
- var rtl = this.dataview.getInherited().rtl,
- parentEl = el.up('');
- if (rtl) {
- return parentEl.getViewRegion().right - el.getRegion().right + el.getMargin('r');
- } else {
- return el.getX() - parentEl.getX() - el.getMargin('l') - parentEl.getPadding('l');
- }
- },
- /**
- * Caches the records from a store locally for comparison later
- * @param {Ext.data.Store} store The store to cache data from
- */
- cacheStoreData: function(store) {
- var cachedStoreData = this.cachedStoreData = {};
- store.each(function(record) {
- cachedStoreData[record.internalId] = record;
- });
- },
- /**
- * Returns all records that were already in the DataView
- * @return {Object} All existing records
- */
- getExisting: function() {
- return this.cachedStoreData;
- },
- /**
- * Returns the total number of items that are currently visible in the DataView
- * @return {Number} The number of existing items
- */
- getExistingCount: function() {
- var count = 0,
- items = this.getExisting(),
- k;
- // eslint-disable-line no-unused-vars
- for (k in items) {
- count++;
- }
- return count;
- },
- /**
- * Returns all records in the given store that were not already present
- * @param {Ext.data.Store} store The updated store instance
- * @return {Object} Object of records not already present in the dataview in format {id: record}
- */
- getAdded: function(store) {
- var cachedStoreData = this.cachedStoreData,
- added = {};
- store.each(function(record) {
- if (cachedStoreData[record.internalId] == null) {
- added[record.internalId] = record;
- }
- });
- return added;
- },
- /**
- * Returns all records that are present in the DataView but not the new store
- * @param {Ext.data.Store} store The updated store instance
- * @return {Array} Array of records that used to be present
- */
- getRemoved: function(store) {
- var cachedStoreData = this.cachedStoreData,
- removed = {},
- id;
- for (id in cachedStoreData) {
- // eslint-disable-next-line brace-style, semi
- if (store.findBy(function(record) {
- return record.internalId === id;
- }) === -1) {
- removed[id] = cachedStoreData[id];
- }
- }
- return removed;
- },
- /**
- * Returns all records that are already present and are still present in the new store
- * @param {Ext.data.Store} store The updated store instance
- * @return {Object} Object of records that are still present from last time in format
- * {id: record}
- */
- getRemaining: function(store) {
- var cachedStoreData = this.cachedStoreData,
- remaining = {};
- store.each(function(record) {
- if (cachedStoreData[record.internalId] != null) {
- remaining[record.internalId] = record;
- }
- });
- return remaining;
- }
- });
- /**
- *
- */
- Ext.define('Ext.ux.DataView.DragSelector', {
- requires: [
- 'Ext.dd.DragTracker',
- 'Ext.util.Region'
- ],
- alias: 'plugin.dataviewdragselector',
- /**
- * Initializes the plugin by setting up the drag tracker
- */
- init: function(dataview) {
- var scroller = dataview.getScrollable();
- // If the client dataview is scrollable, and this is a PointerEvents device
- // we cannot intercept the pointer to inplement dragselect.
- if (scroller && (scroller.getX() || scroller.getY()) && (Ext.supports.PointerEvents || Ext.supports.MSPointerEvents)) {
- //<debug>
- Ext.log.warn('DragSelector not available on PointerEvent devices');
- //</debug>
- return;
- }
- /**
- * @property dataview
- * @type Ext.view.View
- * The DataView bound to this instance
- */
- this.dataview = dataview;
- dataview.mon(dataview, {
- beforecontainerclick: this.cancelClick,
- scope: this,
- render: {
- fn: this.onRender,
- scope: this,
- single: true
- }
- });
- },
- /**
- * @private
- * Called when the attached DataView is rendered. This sets up the DragTracker instance
- * that will be used to created a dragged selection area
- */
- onRender: function() {
- /**
- * @property tracker
- * @type Ext.dd.DragTracker
- * The DragTracker attached to this instance. Note that the 4 on* functions are called
- * in the scope of the DragTracker ('this' refers to the DragTracker inside those
- * functions), so we pass a reference to the DragSelector so that we can call
- * this class's functions.
- */
- this.tracker = Ext.create('Ext.dd.DragTracker', {
- dataview: this.dataview,
- el: this.dataview.el,
- onBeforeStart: this.onBeforeStart,
- onStart: this.onStart.bind(this),
- onDrag: this.onDrag.bind(this),
- onEnd: Ext.Function.createDelayed(this.onEnd, 100, this)
- });
- /**
- * @property dragRegion
- * @type Ext.util.Region
- * Represents the region currently dragged out by the user. This is used to figure out
- * which dataview nodes are in the selected area and to set the size of the Proxy element
- * used to highlight the current drag area
- */
- this.dragRegion = Ext.create('Ext.util.Region');
- },
- /**
- * @private
- * Listener attached to the DragTracker's onBeforeStart event. Returns false if the drag
- * didn't start within the DataView's el
- */
- onBeforeStart: function(e) {
- return e.target === this.dataview.getEl().dom;
- },
- /**
- * @private
- * Listener attached to the DragTracker's onStart event. Cancel's the DataView's containerclick
- * event from firing and sets the start co-ordinates of the Proxy element. Clears any existing
- * DataView selection
- * @param {Ext.event.Event} e The click event
- */
- onStart: function(e) {
- var dataview = this.dataview;
- // Flag which controls whether the cancelClick method vetoes the processing of the
- // DataView's containerclick event.
- // On IE (where else), this needs to remain set for a millisecond after mouseup because
- // even though the mouse has moved, the mouseup will still trigger a click event.
- this.dragging = true;
- // here we reset and show the selection proxy element and cache the regions each item
- // in the dataview take up
- this.fillRegions();
- this.getProxy().show();
- dataview.getSelectionModel().deselectAll();
- },
- /**
- * @private
- * Reusable handler that's used to cancel the container click event when dragging on the
- * dataview. See onStart for details
- */
- cancelClick: function() {
- return !this.dragging;
- },
- /**
- * @private
- * Listener attached to the DragTracker's onDrag event. Figures out how large the drag selection
- * area should be and updates the proxy element's size to match. Then iterates over all of the
- * rendered items and marks them selected if the drag region touches them
- * @param {Ext.event.Event} e The drag event
- */
- onDrag: function(e) {
- var selModel = this.dataview.getSelectionModel(),
- dragRegion = this.dragRegion,
- bodyRegion = this.bodyRegion,
- proxy = this.getProxy(),
- regions = this.regions,
- length = regions.length,
- startXY = this.tracker.startXY,
- currentXY = this.tracker.getXY(),
- minX = Math.min(startXY[0], currentXY[0]),
- minY = Math.min(startXY[1], currentXY[1]),
- width = Math.abs(startXY[0] - currentXY[0]),
- height = Math.abs(startXY[1] - currentXY[1]),
- region, selected, i;
- Ext.apply(dragRegion, {
- top: minY,
- left: minX,
- right: minX + width,
- bottom: minY + height
- });
- dragRegion.constrainTo(bodyRegion);
- proxy.setBox(dragRegion);
- for (i = 0; i < length; i++) {
- region = regions[i];
- selected = dragRegion.intersect(region);
- if (selected) {
- selModel.select(i, true);
- } else {
- selModel.deselect(i);
- }
- }
- },
- /**
- * @method
- * @private
- * Listener attached to the DragTracker's onEnd event. This is a delayed function which
- * executes 1 millisecond after it has been called. This is because the dragging flag must
- * remain active to cancel the containerclick event which the mouseup event will trigger.
- * @param {Ext.event.Event} e The event object
- */
- onEnd: function(e) {
- var dataview = this.dataview,
- selModel = dataview.getSelectionModel();
- // eslint-disable-line no-unused-vars
- this.dragging = false;
- this.getProxy().hide();
- },
- /**
- * @private
- * Creates a Proxy element that will be used to highlight the drag selection region
- * @return {Ext.Element} The Proxy element
- */
- getProxy: function() {
- if (!this.proxy) {
- this.proxy = this.dataview.getEl().createChild({
- tag: 'div',
- cls: 'x-view-selector'
- });
- }
- return this.proxy;
- },
- /**
- * @private
- * Gets the region taken up by each rendered node in the DataView. We use these regions
- * to figure out which nodes to select based on the selector region the user has dragged out
- */
- fillRegions: function() {
- var dataview = this.dataview,
- regions = this.regions = [];
- dataview.all.each(function(node) {
- regions.push(node.getRegion());
- });
- this.bodyRegion = dataview.getEl().getRegion();
- }
- });
- /**
- * ## Basic DataView with Draggable mixin.
- *
- * Ext.Loader.setPath('Ext.ux', '../../../SDK/extjs/examples/ux');
- *
- * Ext.define('My.cool.View', {
- * extend: 'Ext.view.View',
- *
- * mixins: {
- * draggable: 'Ext.ux.DataView.Draggable'
- * },
- *
- * initComponent: function() {
- * this.mixins.draggable.init(this, {
- * ddConfig: {
- * ddGroup: 'someGroup'
- * }
- * });
- *
- * this.callParent(arguments);
- * }
- * });
- *
- * Ext.onReady(function () {
- * Ext.create('Ext.data.Store', {
- * storeId: 'baseball',
- * fields: ['team', 'established'],
- * data: [
- * { team: 'Atlanta Braves', established: '1871' },
- * { team: 'Miami Marlins', established: '1993' },
- * { team: 'New York Mets', established: '1962' },
- * { team: 'Philadelphia Phillies', established: '1883' },
- * { team: 'Washington Nationals', established: '1969' }
- * ]
- * });
- *
- * Ext.create('My.cool.View', {
- * store: Ext.StoreMgr.get('baseball'),
- * tpl: [
- * '<tpl for=".">',
- * '<p class="team">',
- * 'The {team} were founded in {established}.',
- * '</p>',
- * '</tpl>'
- * ],
- * itemSelector: 'p.team',
- * renderTo: Ext.getBody()
- * });
- * });
- */
- Ext.define('Ext.ux.DataView.Draggable', {
- requires: 'Ext.dd.DragZone',
- /**
- * @cfg {String} ghostCls The CSS class added to the outermost element of the created
- * ghost proxy (defaults to 'x-dataview-draggable-ghost')
- */
- ghostCls: 'x-dataview-draggable-ghost',
- /**
- * @cfg {Ext.XTemplate/Array} ghostTpl The template used in the ghost DataView
- */
- ghostTpl: [
- '<tpl for=".">',
- '{title}',
- // eslint-disable-line indent
- '</tpl>'
- ],
- /**
- * @cfg {Object} ddConfig Config object that is applied to the internally created DragZone
- */
- /**
- * @cfg {String} ghostConfig Config object that is used to configure the internally created
- * DataView
- */
- init: function(dataview, config) {
- /**
- * @property dataview
- * @type Ext.view.View
- * The Ext.view.View instance that this DragZone is attached to
- */
- this.dataview = dataview;
- dataview.on('render', this.onRender, this);
- Ext.apply(this, {
- itemSelector: dataview.itemSelector,
- ghostConfig: {}
- }, config || {});
- Ext.applyIf(this.ghostConfig, {
- itemSelector: 'img',
- cls: this.ghostCls,
- tpl: this.ghostTpl
- });
- },
- /**
- * @private
- * Called when the attached DataView is rendered. Sets up the internal DragZone
- */
- onRender: function() {
- var me = this,
- config = Ext.apply({}, me.ddConfig || {}, {
- dvDraggable: me,
- dataview: me.dataview,
- getDragData: me.getDragData,
- getTreeNode: me.getTreeNode,
- afterRepair: me.afterRepair,
- getRepairXY: me.getRepairXY
- });
- /**
- * @property dragZone
- * @type Ext.dd.DragZone
- * The attached DragZone instane
- */
- me.dragZone = Ext.create('Ext.dd.DragZone', me.dataview.getEl(), config);
- // This is for https://www.w3.org/TR/pointerevents/ platforms.
- // On these platforms, the pointerdown event (single touchstart) is reserved for
- // initiating a scroll gesture. Setting the items draggable defeats that and
- // enables the touchstart event to trigger a drag.
- //
- // Two finger dragging will still scroll on these platforms.
- me.dataview.setItemsDraggable(true);
- },
- getDragData: function(e) {
- var draggable = this.dvDraggable,
- dataview = this.dataview,
- selModel = dataview.getSelectionModel(),
- target = e.getTarget(draggable.itemSelector),
- selected, dragData;
- if (target) {
- // preventDefault is needed here to avoid the browser dragging the image
- // instead of dragging the container like it's supposed to
- e.preventDefault();
- if (!dataview.isSelected(target)) {
- selModel.select(dataview.getRecord(target));
- }
- selected = dataview.getSelectedNodes();
- dragData = {
- copy: true,
- nodes: selected,
- records: selModel.getSelection(),
- item: true
- };
- if (selected.length === 1) {
- dragData.single = true;
- dragData.ddel = target;
- } else {
- dragData.multi = true;
- dragData.ddel = draggable.prepareGhost(selModel.getSelection());
- }
- return dragData;
- }
- return false;
- },
- getTreeNode: function() {},
- // console.log('test');
- afterRepair: function() {
- var nodes = this.dragData.nodes,
- length = nodes.length,
- i;
- this.dragging = false;
- // FIXME: Ext.fly does not work here for some reason, only frames the last node
- for (i = 0; i < length; i++) {
- Ext.get(nodes[i]).frame('#8db2e3', 1);
- }
- },
- /**
- * @private
- * Returns the x and y co-ordinates that the dragged item should be animated back to if it
- * was dropped on an invalid drop target. If we're dragging more than one item we don't animate
- * back and just allow afterRepair to frame each dropped item.
- */
- getRepairXY: function(e) {
- var repairEl, repairXY;
- if (this.dragData.multi) {
- return false;
- } else {
- repairEl = Ext.get(this.dragData.ddel);
- repairXY = repairEl.getXY();
- // take the item's margins and padding into account to make the repair animation
- // line up perfectly
- repairXY[0] += repairEl.getPadding('t') + repairEl.getMargin('t');
- repairXY[1] += repairEl.getPadding('l') + repairEl.getMargin('l');
- return repairXY;
- }
- },
- /**
- * Updates the internal ghost DataView by ensuring it is rendered and contains the correct
- * records
- * @param {Array} records The set of records that is currently selected in the parent DataView
- * @return {HTMLElement} The Ghost DataView's encapsulating HTMLElement.
- */
- prepareGhost: function(records) {
- return this.createGhost(records).getEl().dom;
- },
- /**
- * @private
- * Creates the 'ghost' DataView that follows the mouse cursor during the drag operation.
- * This div is usually a lighter-weight representation of just the nodes that are selected
- * in the parent DataView.
- */
- createGhost: function(records) {
- var me = this,
- store;
- if (me.ghost) {
- (store = me.ghost.store).loadRecords(records);
- } else {
- store = Ext.create('Ext.data.Store', {
- model: records[0].self
- });
- store.loadRecords(records);
- me.ghost = Ext.create('Ext.view.View', Ext.apply({
- renderTo: document.createElement('div'),
- store: store
- }, me.ghostConfig));
- me.ghost.container.skipGarbageCollection = me.ghost.el.skipGarbageCollection = true;
- }
- store.clearData();
- return me.ghost;
- },
- destroy: function() {
- var ghost = this.ghost;
- if (ghost) {
- ghost.container.destroy();
- ghost.destroy();
- }
- this.callParent();
- }
- });
- /**
- *
- */
- Ext.define('Ext.ux.DataView.LabelEditor', {
- extend: 'Ext.Editor',
- alias: 'plugin.dataviewlabeleditor',
- alignment: 'tl-tl',
- completeOnEnter: true,
- cancelOnEsc: true,
- shim: false,
- autoSize: {
- width: 'boundEl',
- height: 'field'
- },
- labelSelector: 'x-editable',
- requires: [
- 'Ext.form.field.Text'
- ],
- constructor: function(config) {
- config.field = config.field || Ext.create('Ext.form.field.Text', {
- allowOnlyWhitespace: false,
- selectOnFocus: true
- });
- this.callParent([
- config
- ]);
- },
- init: function(view) {
- this.view = view;
- this.mon(view, 'afterrender', this.bindEvents, this);
- this.on('complete', this.onSave, this);
- },
- // initialize events
- bindEvents: function() {
- this.mon(this.view.getEl(), {
- click: {
- fn: this.onClick,
- scope: this
- }
- });
- },
- // on mousedown show editor
- onClick: function(e, target) {
- var me = this,
- item, record;
- if (Ext.fly(target).hasCls(me.labelSelector) && !me.editing && !e.ctrlKey && !e.shiftKey) {
- e.stopEvent();
- item = me.view.findItemByChild(target);
- record = me.view.store.getAt(me.view.indexOf(item));
- me.startEdit(target, record.data[me.dataIndex]);
- me.activeRecord = record;
- } else if (me.editing) {
- me.field.blur();
- e.preventDefault();
- }
- },
- // update record
- onSave: function(ed, value) {
- this.activeRecord.set(this.dataIndex, value);
- }
- });
- /**
- * @class Ext.ux.DataViewTransition
- * Transition plugin for DataViews
- */
- /* eslint-disable vars-on-top, one-var */
- Ext.ux.DataViewTransition = Ext.extend(Object, {
- /**
- * @property defaults
- * @type Object
- * Default configuration options for all DataViewTransition instances
- */
- defaults: {
- duration: 750,
- idProperty: 'id'
- },
- /**
- * Creates the plugin instance, applies defaults
- * @constructor
- * @param {Object} config Optional config object
- */
- constructor: function(config) {
- Ext.apply(this, config || {}, this.defaults);
- },
- /**
- * Initializes the transition plugin. Overrides the dataview's default refresh function
- * @param {Ext.view.View} dataview The dataview
- */
- init: function(dataview) {
- /**
- * @property dataview
- * @type Ext.view.View
- * Reference to the DataView this instance is bound to
- */
- this.dataview = dataview;
- var idProperty = this.idProperty;
- dataview.blockRefresh = true;
- dataview.updateIndexes = Ext.Function.createSequence(dataview.updateIndexes, function() {
- this.getTargetEl().select(this.itemSelector).each(function(element, composite, index) {
- element.id = element.dom.id = Ext.util.Format.format("{0}-{1}", dataview.id, dataview.store.getAt(index).get(idProperty));
- }, this);
- }, dataview);
- /**
- * @property dataviewID
- * @type String
- * The string ID of the DataView component. This is used internally when animating
- * child objects
- */
- this.dataviewID = dataview.id;
- /**
- * @property cachedStoreData
- * @type Object
- * A cache of existing store data, keyed by id. This is used to determine
- * whether any items were added or removed from the store on data change
- */
- this.cachedStoreData = {};
- // var store = dataview.store;
- // catch the store data with the snapshot immediately
- this.cacheStoreData(dataview.store.snapshot);
- dataview.store.on('datachanged', function(store) {
- var parentEl = dataview.getTargetEl(),
- calcItem = store.getAt(0),
- added = this.getAdded(store),
- removed = this.getRemoved(store),
- previous = this.getRemaining(store);
- // hide old items
- Ext.each(removed, function(item) {
- Ext.fly(this.dataviewID + '-' + item.get(this.idProperty)).animate({
- remove: false,
- duration: duration,
- opacity: 0,
- useDisplay: true
- });
- }, this);
- // store is empty
- if (calcItem == undefined) {
- // eslint-disable-line eqeqeq
- this.cacheStoreData(store);
- return;
- }
- var el = Ext.get(this.dataviewID + "-" + calcItem.get(this.idProperty)),
- // calculate the number of rows and columns we have
- itemWidth = el.getMargin('lr') + el.getWidth(),
- itemHeight = el.getMargin('bt') + el.getHeight(),
- dvWidth = parentEl.getWidth(),
- columns = Math.floor(dvWidth / itemWidth);
- // make sure the correct styles are applied to the parent element
- parentEl.applyStyles({
- display: 'block',
- position: 'relative'
- });
- // stores the current top and left values for each element (discovered below)
- var oldPositions = {},
- newPositions = {},
- elCache = {};
- // find current positions of each element and save a reference in the elCache
- Ext.iterate(previous, function(id, item) {
- // eslint-disable-next-line no-redeclare
- var id = item.get(this.idProperty),
- el = elCache[id] = Ext.get(this.dataviewID + '-' + id);
- oldPositions[id] = {
- top: el.getY() - parentEl.getY() - el.getMargin('t') - parentEl.getPadding('t'),
- left: el.getX() - parentEl.getX() - el.getMargin('l') - parentEl.getPadding('l')
- };
- }, this);
- // set absolute positioning on all DataView items. We need to set position, left and
- // top at the same time to avoid any flickering
- Ext.iterate(previous, function(id, item) {
- var oldPos = oldPositions[id],
- el = elCache[id];
- if (el.getStyle('position') !== 'absolute') {
- elCache[id].applyStyles({
- position: 'absolute',
- left: oldPos.left + "px",
- top: oldPos.top + "px",
- // we set the width here to make ListViews work correctly.
- // This is not needed for DataViews
- width: el.getWidth(!Ext.isIE || Ext.isStrict),
- height: el.getHeight(!Ext.isIE || Ext.isStrict)
- });
- }
- });
- // get new positions
- var index = 0;
- Ext.iterate(store.data.items, function(item) {
- var id = item.get(idProperty),
- column = index % columns,
- row = Math.floor(index / columns),
- top = row * itemHeight,
- left = column * itemWidth;
- newPositions[id] = {
- top: top,
- left: left
- };
- index++;
- }, this);
- // do the movements
- var startTime = new Date(),
- duration = this.duration,
- dataviewID = this.dataviewID,
- doAnimate = function() {
- var elapsed = new Date() - startTime,
- fraction = elapsed / duration,
- id;
- if (fraction >= 1) {
- for (id in newPositions) {
- Ext.fly(dataviewID + '-' + id).applyStyles({
- top: newPositions[id].top + "px",
- left: newPositions[id].left + "px"
- });
- }
- Ext.TaskManager.stop(task);
- } else {
- // move each item
- for (id in newPositions) {
- if (!previous[id]) {
-
- continue;
- }
- var oldPos = oldPositions[id],
- newPos = newPositions[id],
- oldTop = oldPos.top,
- newTop = newPos.top,
- oldLeft = oldPos.left,
- newLeft = newPos.left,
- diffTop = fraction * Math.abs(oldTop - newTop),
- diffLeft = fraction * Math.abs(oldLeft - newLeft),
- midTop = oldTop > newTop ? oldTop - diffTop : oldTop + diffTop,
- midLeft = oldLeft > newLeft ? oldLeft - diffLeft : oldLeft + diffLeft;
- Ext.fly(dataviewID + '-' + id).applyStyles({
- top: midTop + "px",
- left: midLeft + "px"
- });
- }
- }
- },
- task = {
- run: doAnimate,
- interval: 20,
- scope: this
- };
- Ext.TaskManager.start(task);
- //<debug>
- var count = 0;
- for (var k in added) {
- // eslint-disable-line no-unused-vars
- count++;
- }
- if (Ext.global.console && Ext.global.console.log) {
- Ext.global.console.log('added:', count);
- }
- //</debug>
- // show new items
- Ext.iterate(added, function(id, item) {
- Ext.fly(this.dataviewID + '-' + item.get(this.idProperty)).applyStyles({
- top: newPositions[item.get(this.idProperty)].top + "px",
- left: newPositions[item.get(this.idProperty)].left + "px"
- });
- Ext.fly(this.dataviewID + '-' + item.get(this.idProperty)).animate({
- remove: false,
- duration: duration,
- opacity: 1
- });
- }, this);
- this.cacheStoreData(store);
- }, this);
- },
- /**
- * Caches the records from a store locally for comparison later
- * @param {Ext.data.Store} store The store to cache data from
- */
- cacheStoreData: function(store) {
- this.cachedStoreData = {};
- store.each(function(record) {
- this.cachedStoreData[record.get(this.idProperty)] = record;
- }, this);
- },
- /**
- * Returns all records that were already in the DataView
- * @return {Object} All existing records
- */
- getExisting: function() {
- return this.cachedStoreData;
- },
- /**
- * Returns the total number of items that are currently visible in the DataView
- * @return {Number} The number of existing items
- */
- getExistingCount: function() {
- var count = 0,
- items = this.getExisting();
- for (var k in items) {
- // eslint-disable-line no-unused-vars
- count++;
- }
- return count;
- },
- /**
- * Returns all records in the given store that were not already present
- * @param {Ext.data.Store} store The updated store instance
- * @return {Object} Object of records not already present in the dataview in format {id: record}
- */
- getAdded: function(store) {
- var added = {};
- store.each(function(record) {
- // eslint-disable-next-line eqeqeq
- if (this.cachedStoreData[record.get(this.idProperty)] == undefined) {
- added[record.get(this.idProperty)] = record;
- }
- }, this);
- return added;
- },
- /**
- * Returns all records that are present in the DataView but not the new store
- * @param {Ext.data.Store} store The updated store instance
- * @return {Array} Array of records that used to be present
- */
- getRemoved: function(store) {
- var removed = [];
- for (var id in this.cachedStoreData) {
- if (store.findExact(this.idProperty, Number(id)) === -1) {
- removed.push(this.cachedStoreData[id]);
- }
- }
- return removed;
- },
- /**
- * Returns all records that are already present and are still present in the new store
- * @param {Ext.data.Store} store The updated store instance
- * @return {Object} Object of records that are still present from last time in format
- * {id: record}
- */
- getRemaining: function(store) {
- var remaining = {};
- store.each(function(record) {
- /* eslint-disable-next-line eqeqeq */
- if (this.cachedStoreData[record.get(this.idProperty)] != undefined) {
- remaining[record.get(this.idProperty)] = record;
- }
- }, this);
- return remaining;
- }
- });
- /**
- * An explorer component for navigating hierarchical content. Consists of a breadcrumb bar
- * at the top, tree navigation on the left, and a center panel which displays the contents
- * of a given node.
- */
- Ext.define('Ext.ux.Explorer', {
- extend: 'Ext.panel.Panel',
- xtype: 'explorer',
- requires: [
- 'Ext.layout.container.Border',
- 'Ext.toolbar.Breadcrumb',
- 'Ext.tree.Panel'
- ],
- config: {
- /**
- * @cfg {Object} breadcrumb
- * Configuration object for the breadcrumb toolbar
- */
- breadcrumb: {
- dock: 'top',
- xtype: 'breadcrumb',
- reference: 'breadcrumb'
- },
- /* eslint-disable max-len, indent */
- /**
- * @cfg {Object} contentView
- * Configuration object for the "content" data view
- */
- contentView: {
- xtype: 'dataview',
- reference: 'contentView',
- region: 'center',
- cls: Ext.baseCSSPrefix + 'explorer-view',
- itemSelector: '.' + Ext.baseCSSPrefix + 'explorer-item',
- 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>'
- },
- /* eslint-enable max-len, indent */
- /**
- * @cfg {Ext.data.TreeStore} store
- * The TreeStore to use as the data source
- */
- store: null,
- /**
- * @cfg {Object} tree
- * Configuration object for the tree
- */
- tree: {
- xtype: 'treepanel',
- reference: 'tree',
- region: 'west',
- width: 200
- }
- },
- renderConfig: {
- /**
- * @cfg {Ext.data.TreeModel} selection
- * The selected node
- * @accessor
- */
- selection: null
- },
- layout: 'border',
- referenceHolder: true,
- defaultListenerScope: true,
- cls: Ext.baseCSSPrefix + 'explorer',
- initComponent: function() {
- var me = this,
- store = me.getStore();
- //<debug>
- if (!store) {
- Ext.raise('Ext.ux.Explorer requires a store.');
- }
- //</debug>
- me.dockedItems = [
- me.getBreadcrumb()
- ];
- me.items = [
- me.getTree(),
- me.getContentView()
- ];
- me.callParent();
- },
- applyBreadcrumb: function(breadcrumb) {
- var store = this.getStore();
- breadcrumb = Ext.create(Ext.apply({
- store: store,
- selection: store.getRoot()
- }, breadcrumb));
- breadcrumb.on('selectionchange', '_onBreadcrumbSelectionChange', this);
- return breadcrumb;
- },
- applyContentView: function(contentView) {
- /**
- * @property {Ext.data.Store} contentStore
- * @private
- * The backing store for the content view
- */
- var contentStore = this.contentStore = new Ext.data.Store({
- model: this.getStore().model
- });
- contentView = Ext.create(Ext.apply({
- store: contentStore
- }, contentView));
- return contentView;
- },
- applyTree: function(tree) {
- tree = Ext.create(Ext.apply({
- store: this.getStore()
- }, tree));
- tree.on('selectionchange', '_onTreeSelectionChange', this);
- return tree;
- },
- updateSelection: function(node) {
- var me = this,
- refs = me.getReferences(),
- breadcrumb = refs.breadcrumb,
- tree = refs.tree,
- treeSelectionModel = tree.getSelectionModel(),
- contentStore = me.contentStore,
- parentNode, treeView;
- if (breadcrumb.getSelection() !== node) {
- breadcrumb.setSelection(node);
- }
- if (treeSelectionModel.getSelection()[0] !== node) {
- treeSelectionModel.select([
- node
- ]);
- parentNode = node.parentNode;
- if (parentNode) {
- parentNode.expand();
- }
- treeView = tree.getView();
- treeView.scrollRowIntoView(treeView.getRow(node));
- }
- contentStore.removeAll();
- contentStore.add(node.hasChildNodes() ? node.childNodes : [
- node
- ]);
- },
- updateStore: function(store) {
- this.getBreadcrumb().setStore(store);
- },
- privates: {
- /**
- * Handles the tree's selectionchange event
- * @private
- * @param {Ext.tree.Panel} tree
- * @param {Ext.data.TreeModel[]} selection
- */
- _onTreeSelectionChange: function(tree, selection) {
- this.setSelection(selection[0]);
- },
- /**
- * Handles the breadcrumb bar's selectionchange event
- */
- _onBreadcrumbSelectionChange: function(breadcrumb, selection) {
- this.setSelection(selection);
- }
- }
- });
- /**
- * A plugin for Field Components which creates clones of the Field for as
- * long as the user keeps filling them. Leaving the final one blank ends the repeating series.
- *
- * Usage:
- *
- * items: [{
- * xtype: 'combo',
- * plugins: {
- * fieldreplicator: true
- * },
- * triggerAction: 'all',
- * fieldLabel: 'Select recipient',
- * store: recipientStore
- * }]
- *
- */
- Ext.define('Ext.ux.FieldReplicator', {
- alias: 'plugin.fieldreplicator',
- init: function(field) {
- // Assign the field an id grouping it with fields cloned from it. If it already
- // has an id that means it is itself a clone.
- if (!field.replicatorId) {
- field.replicatorId = Ext.id();
- }
- field.on('blur', this.onBlur, this);
- },
- onBlur: function(field) {
- var ownerCt = field.ownerCt,
- replicatorId = field.replicatorId,
- isEmpty = Ext.isEmpty(field.getRawValue()),
- siblings = ownerCt.query('[replicatorId=' + replicatorId + ']'),
- isLastInGroup = siblings[siblings.length - 1] === field,
- clone, idx;
- // If a field before the final one was blanked out, remove it
- if (isEmpty && !isLastInGroup) {
- Ext.defer(field.destroy, 10, field);
- }
- // delay to allow tab key to move focus first
- // If the field is the last in the list and has a value, add a cloned field after it
- else if (!isEmpty && isLastInGroup) {
- if (field.onReplicate) {
- field.onReplicate();
- }
- clone = field.cloneConfig({
- replicatorId: replicatorId
- });
- idx = ownerCt.items.indexOf(field);
- ownerCt.add(idx + 1, clone);
- }
- }
- });
- /**
- * The GMap Panel UX extends `Ext.panel.Panel` in order to display Google Maps.
- *
- * It is important to note that you must include the following Google Maps API above
- * bootstrap.js in your application's index.html file (or equivalent).
- *
- * <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?v=3&sensor=false"></script>
- *
- * It is important to note that due to the Google Maps loader, you cannot currently include
- * the above JS resource in the Cmd generated app.json file. Doing so interferes with the loading of
- * Ext JS and Google Maps.
- *
- * The following example creates a window containing a GMap Panel. In this case, the center
- * is set as geoCodeAddr, which is a string that Google translates into longitude and latitude.
- *
- * var mapwin = Ext.create('Ext.Window', {
- * layout: 'fit',
- * title: 'GMap Window',
- * width: 450,
- * height: 250,
- * items: {
- * xtype: 'gmappanel',
- * gmapType: 'map',
- * center: {
- * geoCodeAddr: "221B Baker Street",
- * marker: {
- * title: 'Holmes Home'
- * }
- * },
- * mapOptions : {
- * mapTypeId: google.maps.MapTypeId.ROADMAP
- * }
- * }
- * }).show();
- *
- */
- Ext.define('Ext.ux.GMapPanel', {
- extend: 'Ext.panel.Panel',
- alias: 'widget.gmappanel',
- requires: [
- 'Ext.window.MessageBox'
- ],
- initComponent: function() {
- Ext.applyIf(this, {
- plain: true,
- gmapType: 'map',
- border: false
- });
- this.callParent();
- },
- onBoxReady: function() {
- var center = this.center;
- this.callParent(arguments);
- if (center) {
- if (center.geoCodeAddr) {
- this.lookupCode(center.geoCodeAddr, center.marker);
- } else {
- this.createMap(center);
- }
- } else {
- Ext.raise('center is required');
- }
- },
- createMap: function(center, marker) {
- var options = Ext.apply({}, this.mapOptions);
- /* global google */
- options = Ext.applyIf(options, {
- zoom: 14,
- center: center,
- mapTypeId: google.maps.MapTypeId.HYBRID
- });
- this.gmap = new google.maps.Map(this.body.dom, options);
- if (marker) {
- this.addMarker(Ext.applyIf(marker, {
- position: center
- }));
- }
- Ext.each(this.markers, this.addMarker, this);
- this.fireEvent('mapready', this, this.gmap);
- },
- addMarker: function(marker) {
- var o;
- marker = Ext.apply({
- map: this.gmap
- }, marker);
- if (!marker.position) {
- marker.position = new google.maps.LatLng(marker.lat, marker.lng);
- }
- o = new google.maps.Marker(marker);
- Ext.Object.each(marker.listeners, function(name, fn) {
- google.maps.event.addListener(o, name, fn);
- });
- return o;
- },
- lookupCode: function(addr, marker) {
- this.geocoder = new google.maps.Geocoder();
- this.geocoder.geocode({
- address: addr
- }, Ext.Function.bind(this.onLookupComplete, this, [
- marker
- ], true));
- },
- onLookupComplete: function(data, response, marker) {
- if (response !== 'OK') {
- Ext.MessageBox.alert('Error', 'An error occured: "' + response + '"');
- return;
- }
- this.createMap(data[0].geometry.location, marker);
- },
- afterComponentLayout: function(w, h) {
- this.callParent(arguments);
- this.redraw();
- },
- redraw: function() {
- var map = this.gmap;
- if (map) {
- google.maps.event.trigger(map, 'resize');
- }
- }
- });
- /**
- * Barebones iframe implementation.
- */
- Ext.define('Ext.ux.IFrame', {
- extend: 'Ext.Component',
- alias: 'widget.uxiframe',
- loadMask: 'Loading...',
- src: 'about:blank',
- renderTpl: [
- // eslint-disable-next-line max-len
- '<iframe src="{src}" id="{id}-iframeEl" data-ref="iframeEl" name="{frameName}" width="100%" height="100%" frameborder="0"></iframe>'
- ],
- childEls: [
- 'iframeEl'
- ],
- initComponent: function() {
- this.callParent();
- this.frameName = this.frameName || this.id + '-frame';
- },
- initEvents: function() {
- var me = this;
- me.callParent();
- me.iframeEl.on('load', me.onLoad, me);
- },
- initRenderData: function() {
- return Ext.apply(this.callParent(), {
- src: this.src,
- frameName: this.frameName
- });
- },
- getBody: function() {
- var doc = this.getDoc();
- return doc.body || doc.documentElement;
- },
- getDoc: function() {
- try {
- return this.getWin().document;
- } catch (ex) {
- return null;
- }
- },
- getWin: function() {
- var me = this,
- name = me.frameName,
- win = Ext.isIE ? me.iframeEl.dom.contentWindow : window.frames[name];
- return win;
- },
- getFrame: function() {
- var me = this;
- return me.iframeEl.dom;
- },
- onLoad: function() {
- var me = this,
- doc = me.getDoc();
- if (doc) {
- this.el.unmask();
- this.fireEvent('load', this);
- } else if (me.src) {
- this.el.unmask();
- this.fireEvent('error', this);
- }
- },
- load: function(src) {
- var me = this,
- text = me.loadMask,
- frame = me.getFrame();
- if (me.fireEvent('beforeload', me, src) !== false) {
- if (text && me.el) {
- me.el.mask(text);
- }
- frame.src = me.src = (src || me.src);
- }
- }
- });
- /*
- * Note: Event relayers are not needed here because the combination of the gesture system and
- * normal focus/blur will handle it.
- * Tested with the examples/classic/desktop app.
- */
- /*
- * TODO items:
- *
- * Iframe should clean up any Ext.dom.Element wrappers around its window, document
- * documentElement and body when it is destroyed. This helps prevent "Permission Denied"
- * errors in IE when Ext.dom.GarbageCollector tries to access those objects on an orphaned
- * iframe. Permission Denied errors can occur in one of the following 2 scenarios:
- *
- * a. When an iframe is removed from the document, and all references to it have been
- * removed, IE will "clear" the window object. At this point the window object becomes
- * completely inaccessible - accessing any of its properties results in a "Permission
- * Denied" error. http://msdn.microsoft.com/en-us/library/ie/hh180174(v=vs.85).aspx
- *
- * b. When an iframe is unloaded (either by navigating to a new url, or via document.open/
- * document.write, new html and body elements are created and the old the html and body
- * elements are orphaned. Accessing the html and body elements or any of their properties
- * results in a "Permission Denied" error.
- */
- /**
- * Basic status bar component that can be used as the bottom toolbar of any {@link Ext.Panel}.
- * In addition to supporting the standard {@link Ext.toolbar.Toolbar} interface for adding buttons,
- * menus and other items, the StatusBar provides a greedy status element that can be aligned
- * to either side and has convenient methods for setting the status text and icon. You can also
- * indicate that something is processing using the {@link #showBusy} method.
- *
- * Ext.create('Ext.Panel', {
- * title: 'StatusBar',
- * // etc.
- * bbar: Ext.create('Ext.ux.StatusBar', {
- * id: 'my-status',
- *
- * // defaults to use when the status is cleared:
- * defaultText: 'Default status text',
- * defaultIconCls: 'default-icon',
- *
- * // values to set initially:
- * text: 'Ready',
- * iconCls: 'ready-icon',
- *
- * // any standard Toolbar items:
- * items: [{
- * text: 'A Button'
- * }, '-', 'Plain Text']
- * })
- * });
- *
- * // Update the status bar later in code:
- * var sb = Ext.getCmp('my-status');
- * sb.setStatus({
- * text: 'OK',
- * iconCls: 'ok-icon',
- * clear: true // auto-clear after a set interval
- * });
- *
- * // Set the status bar to show that something is processing:
- * sb.showBusy();
- *
- * // processing....
- *
- * sb.clearStatus(); // once completeed
- *
- */
- Ext.define('Ext.ux.statusbar.StatusBar', {
- extend: 'Ext.toolbar.Toolbar',
- xtype: 'statusbar',
- alternateClassName: 'Ext.ux.StatusBar',
- requires: [
- 'Ext.toolbar.TextItem'
- ],
- /**
- * @cfg {String} statusAlign
- * The alignment of the status element within the overall StatusBar layout. When the StatusBar
- * is rendered, it creates an internal div containing the status text and icon. Any additional
- * Toolbar items added in the StatusBar's {@link #cfg-items} config, or added via
- * {@link #method-add} or any of the supported add* methods, will be rendered, in added order,
- * to the opposite side. The status element is greedy, so it will automatically expand
- * to take up all sapce left over by any other items. Example usage:
- *
- * // Create a left-aligned status bar containing a button,
- * // separator and text item that will be right-aligned (default):
- * Ext.create('Ext.Panel', {
- * title: 'StatusBar',
- * // etc.
- * bbar: Ext.create('Ext.ux.statusbar.StatusBar', {
- * defaultText: 'Default status text',
- * id: 'status-id',
- * items: [{
- * text: 'A Button'
- * }, '-', 'Plain Text']
- * })
- * });
- *
- * // By adding the statusAlign config, this will create the
- * // exact same toolbar, except the status and toolbar item
- * // layout will be reversed from the previous example:
- * Ext.create('Ext.Panel', {
- * title: 'StatusBar',
- * // etc.
- * bbar: Ext.create('Ext.ux.statusbar.StatusBar', {
- * defaultText: 'Default status text',
- * id: 'status-id',
- * statusAlign: 'right',
- * items: [{
- * text: 'A Button'
- * }, '-', 'Plain Text']
- * })
- * });
- */
- /**
- * @cfg {String} [defaultText='']
- * The default {@link #text} value. This will be used anytime the status bar is cleared
- * with the `useDefaults:true` option.
- */
- /**
- * @cfg {String} [defaultIconCls='']
- * The default {@link #iconCls} value (see the iconCls docs for additional details about
- * customizing the icon). This will be used anytime the status bar is cleared with the
- * `useDefaults:true` option.
- */
- /**
- * @cfg {String} text
- * A string that will be <b>initially</b> set as the status message. This string
- * will be set as innerHTML (html tags are accepted) for the toolbar item.
- * If not specified, the value set for {@link #defaultText} will be used.
- */
- /**
- * @cfg [iconCls='']
- * @inheritdoc Ext.panel.Header#cfg-iconCls
- * @localdoc **Note:** This CSS class will be **initially** set as the status bar
- * icon. See also {@link #defaultIconCls} and {@link #busyIconCls}.
- *
- * Example usage:
- *
- * // Example CSS rule:
- * .x-statusbar .x-status-custom {
- * padding-left: 25px;
- * background: transparent url(images/custom-icon.gif) no-repeat 3px 2px;
- * }
- *
- * // Setting a default icon:
- * var sb = Ext.create('Ext.ux.statusbar.StatusBar', {
- * defaultIconCls: 'x-status-custom'
- * });
- *
- * // Changing the icon:
- * sb.setStatus({
- * text: 'New status',
- * iconCls: 'x-status-custom'
- * });
- */
- /**
- * @cfg {String} cls
- * The base class applied to the containing element for this component on render.
- */
- cls: 'x-statusbar',
- /**
- * @cfg {String} busyIconCls
- * The default {@link #iconCls} applied when calling {@link #showBusy}.
- * It can be overridden at any time by passing the `iconCls` argument into {@link #showBusy}.
- */
- busyIconCls: 'x-status-busy',
- /**
- * @cfg {String} busyText
- * The default {@link #text} applied when calling {@link #showBusy}.
- * It can be overridden at any time by passing the `text` argument into {@link #showBusy}.
- */
- busyText: 'Loading...',
- /**
- * @cfg {Number} autoClear
- * The number of milliseconds to wait after setting the status via
- * {@link #setStatus} before automatically clearing the status text and icon.
- * Note that this only applies when passing the `clear` argument to {@link #setStatus}
- * since that is the only way to defer clearing the status. This can
- * be overridden by specifying a different `wait` value in {@link #setStatus}.
- * Calls to {@link #clearStatus} always clear the status bar immediately and ignore this value.
- */
- autoClear: 5000,
- /**
- * @cfg {String} emptyText
- * The text string to use if no text has been set. If there are no other items in
- * the toolbar using an empty string (`''`) for this value would end up in the toolbar
- * height collapsing since the empty string will not maintain the toolbar height.
- * Use `''` if the toolbar should collapse in height vertically when no text is
- * specified and there are no other items in the toolbar.
- */
- emptyText: ' ',
- /**
- * @private
- */
- activeThreadId: 0,
- initComponent: function() {
- var right = this.statusAlign === 'right';
- this.callParent(arguments);
- this.currIconCls = this.iconCls || this.defaultIconCls;
- this.statusEl = Ext.create('Ext.toolbar.TextItem', {
- cls: 'x-status-text ' + (this.currIconCls || ''),
- text: this.text || this.defaultText || ''
- });
- if (right) {
- this.cls += ' x-status-right';
- this.add('->');
- this.add(this.statusEl);
- } else {
- this.insert(0, this.statusEl);
- this.insert(1, '->');
- }
- },
- /**
- * Sets the status {@link #text} and/or {@link #iconCls}. Also supports automatically clearing
- * the status that was set after a specified interval.
- *
- * Example usage:
- *
- * // Simple call to update the text
- * statusBar.setStatus('New status');
- *
- * // Set the status and icon, auto-clearing with default options:
- * statusBar.setStatus({
- * text: 'New status',
- * iconCls: 'x-status-custom',
- * clear: true
- * });
- *
- * // Auto-clear with custom options:
- * statusBar.setStatus({
- * text: 'New status',
- * iconCls: 'x-status-custom',
- * clear: {
- * wait: 8000,
- * anim: false,
- * useDefaults: false
- * }
- * });
- *
- * @param {Object/String} config A config object specifying what status to set, or a string
- * assumed to be the status text (and all other options are defaulted as explained below).
- * A config object containing any or all of the following properties can be passed:
- *
- * @param {String} config.text The status text to display. If not specified, any current
- * status text will remain unchanged.
- *
- * @param {String} config.iconCls The CSS class used to customize the status icon (see
- * {@link #iconCls} for details). If not specified, any current iconCls will remain unchanged.
- *
- * @param {Boolean/Number/Object} config.clear Allows you to set an internal callback that will
- * automatically clear the status text and iconCls after a specified amount of time has passed.
- * If clear is not specified, the new status will not be auto-cleared and will stay until
- * updated again or cleared using {@link #clearStatus}. If `true` is passed, the status will be
- * cleared using {@link #autoClear}, {@link #defaultText} and {@link #defaultIconCls} via
- * a fade out animation. If a numeric value is passed, it will be used as the callback interval
- * (in milliseconds), overriding the {@link #autoClear} value. All other options will be
- * defaulted as with the boolean option. To customize any other options, you can pass an object
- * in the format:
- *
- * @param {Number} config.clear.wait The number of milliseconds to wait before clearing
- * (defaults to {@link #autoClear}).
- * @param {Boolean} config.clear.anim False to clear the status immediately once the callback
- * executes (defaults to true which fades the status out).
- * @param {Boolean} config.clear.useDefaults False to completely clear the status text
- * and iconCls (defaults to true which uses {@link #defaultText} and {@link #defaultIconCls}).
- *
- * @return {Ext.ux.statusbar.StatusBar} this
- */
- setStatus: function(config) {
- var me = this,
- c, wait, defaults;
- config = config || {};
- Ext.suspendLayouts();
- if (Ext.isString(config)) {
- config = {
- text: config
- };
- }
- if (config.text !== undefined) {
- me.setText(config.text);
- }
- if (config.iconCls !== undefined) {
- me.setIcon(config.iconCls);
- }
- if (config.clear) {
- c = config.clear;
- wait = me.autoClear;
- defaults = {
- useDefaults: true,
- anim: true
- };
- if (Ext.isObject(c)) {
- c = Ext.applyIf(c, defaults);
- if (c.wait) {
- wait = c.wait;
- }
- } else if (Ext.isNumber(c)) {
- wait = c;
- c = defaults;
- } else if (Ext.isBoolean(c)) {
- c = defaults;
- }
- c.threadId = this.activeThreadId;
- Ext.defer(me.clearStatus, wait, me, [
- c
- ]);
- }
- Ext.resumeLayouts(true);
- return me;
- },
- /**
- * Clears the status {@link #text} and {@link #iconCls}. Also supports clearing via an optional
- * fade out animation.
- *
- * @param {Object} [config] A config object containing any or all of the following properties.
- * If this object is not specified the status will be cleared using the defaults below:
- * @param {Boolean} config.anim True to clear the status by fading out the status element
- * (defaults to false which clears immediately).
- * @param {Boolean} config.useDefaults True to reset the text and icon using
- * {@link #defaultText} and {@link #defaultIconCls} (defaults to false which sets the text
- * to '' and removes any existing icon class).
- *
- * @return {Ext.ux.statusbar.StatusBar} this
- */
- clearStatus: function(config) {
- var me = this,
- statusEl = me.statusEl,
- text, iconCls;
- config = config || {};
- if (me.destroyed || config.threadId && config.threadId !== me.activeThreadId) {
- // this means the current call was made internally, but a newer
- // thread has set a message since this call was deferred. Since
- // we don't want to overwrite a newer message just ignore.
- return me;
- }
- text = config.useDefaults ? me.defaultText : me.emptyText;
- iconCls = config.useDefaults ? (me.defaultIconCls ? me.defaultIconCls : '') : '';
- if (config.anim) {
- // animate the statusEl Ext.Element
- statusEl.el.puff({
- remove: false,
- useDisplay: true,
- callback: function() {
- statusEl.el.show();
- me.setStatus({
- text: text,
- iconCls: iconCls
- });
- }
- });
- } else {
- me.setStatus({
- text: text,
- iconCls: iconCls
- });
- }
- return me;
- },
- /**
- * Convenience method for setting the status text directly. For more flexible options see
- * {@link #setStatus}.
- * @param {String} text (optional) The text to set (defaults to '')
- * @return {Ext.ux.statusbar.StatusBar} this
- */
- setText: function(text) {
- var me = this;
- me.activeThreadId++;
- me.text = text || '';
- if (me.rendered) {
- me.statusEl.setText(me.text);
- }
- return me;
- },
- /**
- * Returns the current status text.
- * @return {String} The status text
- */
- getText: function() {
- return this.text;
- },
- /**
- * Convenience method for setting the status icon directly. For more flexible options see
- * {@link #setStatus}.
- * See {@link #iconCls} for complete details about customizing the icon.
- * @param {String} cls (optional) The icon class to set (defaults to '', and any current
- * icon class is removed)
- * @return {Ext.ux.statusbar.StatusBar} this
- */
- setIcon: function(cls) {
- var me = this;
- me.activeThreadId++;
- cls = cls || '';
- if (me.rendered) {
- if (me.currIconCls) {
- me.statusEl.removeCls(me.currIconCls);
- me.currIconCls = null;
- }
- if (cls.length > 0) {
- me.statusEl.addCls(cls);
- me.currIconCls = cls;
- }
- } else {
- me.currIconCls = cls;
- }
- return me;
- },
- /**
- * Convenience method for setting the status text and icon to special values that are
- * pre-configured to indicate a "busy" state, usually for loading or processing activities.
- *
- * @param {Object/String} config (optional) A config object in the same format supported by
- * {@link #setStatus}, or a string to use as the status text (in which case all other options
- * for setStatus will be defaulted). Use the `text` and/or `iconCls` properties on the config
- * to override the default {@link #busyText} and {@link #busyIconCls} settings. If the config
- * argument is not specified, {@link #busyText} and {@link #busyIconCls} will be used
- * in conjunction with all of the default options for {@link #setStatus}.
- * @return {Ext.ux.statusbar.StatusBar} this
- */
- showBusy: function(config) {
- if (Ext.isString(config)) {
- config = {
- text: config
- };
- }
- config = Ext.applyIf(config || {}, {
- text: this.busyText,
- iconCls: this.busyIconCls
- });
- return this.setStatus(config);
- }
- });
- /**
- * A GridPanel class with live search support.
- */
- Ext.define('Ext.ux.LiveSearchGridPanel', {
- extend: 'Ext.grid.Panel',
- requires: [
- 'Ext.toolbar.TextItem',
- 'Ext.form.field.Checkbox',
- 'Ext.form.field.Text',
- 'Ext.ux.statusbar.StatusBar'
- ],
- /**
- * @private
- * search value initialization
- */
- searchValue: null,
- /**
- * @private
- * The matched positions from the most recent search
- */
- matches: [],
- /**
- * @private
- * The current index matched.
- */
- currentIndex: null,
- /**
- * @private
- * The generated regular expression used for searching.
- */
- searchRegExp: null,
- /**
- * @private
- * Case sensitive mode.
- */
- caseSensitive: false,
- /**
- * @private
- * Regular expression mode.
- */
- regExpMode: false,
- /**
- * @cfg {String} matchCls
- * The matched string css classe.
- */
- matchCls: 'x-livesearch-match',
- defaultStatusText: 'Nothing Found',
- // Component initialization override: adds the top and bottom toolbars and setup
- // headers renderer.
- initComponent: function() {
- var me = this;
- me.tbar = [
- 'Search',
- {
- xtype: 'textfield',
- name: 'searchField',
- hideLabel: true,
- width: 200,
- listeners: {
- change: {
- fn: me.onTextFieldChange,
- scope: this,
- buffer: 500
- }
- }
- },
- {
- xtype: 'button',
- text: '<',
- tooltip: 'Find Previous Row',
- handler: me.onPreviousClick,
- scope: me
- },
- {
- xtype: 'button',
- text: '>',
- tooltip: 'Find Next Row',
- handler: me.onNextClick,
- scope: me
- },
- '-',
- {
- xtype: 'checkbox',
- hideLabel: true,
- margin: '0 0 0 4px',
- handler: me.regExpToggle,
- scope: me
- },
- 'Regular expression',
- {
- xtype: 'checkbox',
- hideLabel: true,
- margin: '0 0 0 4px',
- handler: me.caseSensitiveToggle,
- scope: me
- },
- 'Case sensitive'
- ];
- me.bbar = new Ext.ux.StatusBar({
- defaultText: me.defaultStatusText,
- name: 'searchStatusBar'
- });
- me.callParent(arguments);
- },
- // afterRender override: it adds textfield and statusbar reference and start monitoring
- // keydown events in textfield input
- afterRender: function() {
- var me = this;
- me.callParent(arguments);
- me.textField = me.down('textfield[name=searchField]');
- me.statusBar = me.down('statusbar[name=searchStatusBar]');
- me.view.on('cellkeydown', me.focusTextField, me);
- },
- focusTextField: function(view, td, cellIndex, record, tr, rowIndex, e, eOpts) {
- if (e.getKey() === e.S) {
- e.preventDefault();
- this.textField.focus();
- }
- },
- // detects html tag
- tagsRe: /<[^>]*>/gm,
- // DEL ASCII code
- tagsProtect: '\x0f',
- /**
- * In normal mode it returns the value with protected regexp characters.
- * In regular expression mode it returns the raw value except if the regexp is invalid.
- * @return {String} The value to process or null if the textfield value is blank or invalid.
- * @private
- */
- getSearchValue: function() {
- var me = this,
- value = me.textField.getValue();
- if (value === '') {
- return null;
- }
- if (!me.regExpMode) {
- value = Ext.String.escapeRegex(value);
- } else {
- try {
- new RegExp(value);
- } catch (error) {
- me.statusBar.setStatus({
- text: error.message,
- iconCls: 'x-status-error'
- });
- return null;
- }
- // this is stupid
- if (value === '^' || value === '$') {
- return null;
- }
- }
- return value;
- },
- /**
- * Finds all strings that matches the searched value in each grid cells.
- * @private
- */
- onTextFieldChange: function() {
- var me = this,
- count = 0,
- view = me.view,
- columns = me.visibleColumnManager.getColumns();
- view.refresh();
- // reset the statusbar
- me.statusBar.setStatus({
- text: me.defaultStatusText,
- iconCls: ''
- });
- me.searchValue = me.getSearchValue();
- me.matches = [];
- me.currentIndex = null;
- if (me.searchValue !== null) {
- me.searchRegExp = new RegExp(me.getSearchValue(), 'g' + (me.caseSensitive ? '' : 'i'));
- me.store.each(function(record, idx) {
- var node = view.getNode(record);
- if (node) {
- Ext.Array.forEach(columns, function(column) {
- var cell = Ext.fly(node).down(column.getCellInnerSelector(), true),
- matches, cellHTML, seen;
- if (cell) {
- matches = cell.innerHTML.match(me.tagsRe);
- cellHTML = cell.innerHTML.replace(me.tagsRe, me.tagsProtect);
- // populate indexes array, set currentIndex, and replace wrap
- // matched string in a span
- cellHTML = cellHTML.replace(me.searchRegExp, function(m) {
- ++count;
- if (!seen) {
- me.matches.push({
- record: record,
- column: column
- });
- seen = true;
- }
- return '<span class="' + me.matchCls + '">' + m + '</span>';
- }, me);
- // restore protected tags
- Ext.each(matches, function(match) {
- cellHTML = cellHTML.replace(me.tagsProtect, match);
- });
- // update cell html
- cell.innerHTML = cellHTML;
- }
- });
- }
- }, me);
- // results found
- if (count) {
- me.currentIndex = 0;
- me.gotoCurrent();
- me.statusBar.setStatus({
- text: Ext.String.format('{0} match{1} found.', count, count === 1 ? 'es' : ''),
- iconCls: 'x-status-valid'
- });
- }
- }
- // no results found
- if (me.currentIndex === null) {
- me.getSelectionModel().deselectAll();
- me.textField.focus();
- }
- },
- /**
- * Selects the previous row containing a match.
- * @private
- */
- onPreviousClick: function() {
- var me = this,
- matches = me.matches,
- len = matches.length,
- idx = me.currentIndex;
- if (len) {
- me.currentIndex = idx === 0 ? len - 1 : idx - 1;
- me.gotoCurrent();
- }
- },
- /**
- * Selects the next row containing a match.
- * @private
- */
- onNextClick: function() {
- var me = this,
- matches = me.matches,
- len = matches.length,
- idx = me.currentIndex;
- if (len) {
- me.currentIndex = idx === len - 1 ? 0 : idx + 1;
- me.gotoCurrent();
- }
- },
- /**
- * Switch to case sensitive mode.
- * @private
- */
- caseSensitiveToggle: function(checkbox, checked) {
- this.caseSensitive = checked;
- this.onTextFieldChange();
- },
- /**
- * Switch to regular expression mode
- * @private
- */
- regExpToggle: function(checkbox, checked) {
- this.regExpMode = checked;
- this.onTextFieldChange();
- },
- privates: {
- gotoCurrent: function() {
- var pos = this.matches[this.currentIndex];
- this.getNavigationModel().setPosition(pos.record, pos.column);
- this.getSelectionModel().select(pos.record);
- }
- }
- });
- /**
- * The Preview Plugin enables toggle of a configurable preview of all visible records.
- *
- * Note: This plugin does NOT assert itself against an existing RowBody feature and may conflict
- * with another instance of the same plugin.
- */
- Ext.define('Ext.ux.PreviewPlugin', {
- extend: 'Ext.plugin.Abstract',
- alias: 'plugin.preview',
- requires: [
- 'Ext.grid.feature.RowBody'
- ],
- /**
- * @private
- * css class to use to hide the body
- */
- hideBodyCls: 'x-grid-row-body-hidden',
- /**
- * @cfg {String} bodyField
- * Field to display in the preview. Must be a field within the Model definition
- * that the store is using.
- */
- bodyField: '',
- /**
- * @cfg {Boolean} previewExpanded
- */
- previewExpanded: true,
- /**
- * Plugin may be safely declared on either a panel.Grid or a Grid View/viewConfig
- * @param {Ext.grid.Panel/Ext.view.View} target
- */
- setCmp: function(target) {
- this.callParent(arguments);
- // Resolve grid from view as necessary
- // eslint-disable-next-line vars-on-top
- var me = this,
- grid = me.cmp = target.isXType('gridview') ? target.grid : target,
- bodyField = me.bodyField,
- hideBodyCls = me.hideBodyCls,
- feature = Ext.create('Ext.grid.feature.RowBody', {
- grid: grid,
- getAdditionalData: function(data, idx, model, rowValues) {
- var getAdditionalData = Ext.grid.feature.RowBody.prototype.getAdditionalData,
- additionalData = {
- rowBody: data[bodyField],
- rowBodyCls: grid.getView().previewExpanded ? '' : hideBodyCls
- };
- if (Ext.isFunction(getAdditionalData)) {
- // "this" is the RowBody object hjere. Do not change to "me"
- Ext.apply(additionalData, getAdditionalData.apply(this, arguments));
- }
- return additionalData;
- }
- }),
- initFeature = function(grid, view) {
- view.previewExpanded = me.previewExpanded;
- // By this point, existing features are already in place, so this must be
- // initialized and added
- view.featuresMC.add(feature);
- feature.init(grid);
- };
- // The grid has already created its view
- if (grid.view) {
- initFeature(grid, grid.view);
- } else // At the time a grid creates its plugins, it has not created all the things
- // it needs to create its view correctly.
- // Process the view and init the RowBody Feature as soon as the view is created.
- {
- grid.on({
- viewcreated: initFeature,
- single: true
- });
- }
- },
- /**
- * Toggle between the preview being expanded/hidden on all rows
- * @param {Boolean} expanded Pass true to expand the record and false to not show the preview.
- */
- toggleExpanded: function(expanded) {
- var grid = this.getCmp(),
- view = grid && grid.getView(),
- bufferedRenderer = view.bufferedRenderer,
- scrollManager = view.scrollManager;
- if (grid && view && expanded !== view.previewExpanded) {
- this.previewExpanded = view.previewExpanded = !!expanded;
- view.refreshView();
- // If we are using the touch scroller, ensure that the scroller knows about
- // the correct scrollable range
- if (scrollManager) {
- if (bufferedRenderer) {
- bufferedRenderer.stretchView(view, bufferedRenderer.getScrollHeight(true));
- } else {
- scrollManager.refresh(true);
- }
- }
- }
- }
- });
- /**
- * Plugin for displaying a progressbar inside of a paging toolbar
- * instead of plain text.
- */
- Ext.define('Ext.ux.ProgressBarPager', {
- alias: 'plugin.ux-progressbarpager',
- requires: [
- 'Ext.ProgressBar'
- ],
- /**
- * @cfg {Number} width
- * <p>The default progress bar width. Default is 225.</p>
- */
- width: 225,
- /**
- * @cfg {String} defaultText
- * <p>The text to display while the store is loading. Default is 'Loading...'</p>
- */
- defaultText: 'Loading...',
- /**
- * @cfg {Object} defaultAnimCfg
- * <p>A {@link Ext.fx.Anim Ext.fx.Anim} configuration object.</p>
- */
- defaultAnimCfg: {
- duration: 1000,
- easing: 'bounceOut'
- },
- /**
- * Creates new ProgressBarPager.
- * @param {Object} config Configuration options
- */
- constructor: function(config) {
- if (config) {
- Ext.apply(this, config);
- }
- },
- init: function(parent) {
- var displayItem;
- if (parent.displayInfo) {
- this.parent = parent;
- displayItem = parent.child("#displayItem");
- if (displayItem) {
- parent.remove(displayItem, true);
- }
- this.progressBar = Ext.create('Ext.ProgressBar', {
- text: this.defaultText,
- width: this.width,
- animate: this.defaultAnimCfg,
- style: {
- cursor: 'pointer'
- },
- listeners: {
- el: {
- scope: this,
- click: this.handleProgressBarClick
- }
- }
- });
- parent.displayItem = this.progressBar;
- parent.add(parent.displayItem);
- Ext.apply(parent, this.parentOverrides);
- }
- },
- /**
- * This method handles the click for the progress bar
- * @private
- */
- handleProgressBarClick: function(e) {
- var parent = this.parent,
- displayItem = parent.displayItem,
- box = this.progressBar.getBox(),
- xy = e.getXY(),
- position = xy[0] - box.x,
- store = parent.store,
- pageSize = parent.pageSize || store.pageSize,
- pages = Math.ceil(store.getTotalCount() / pageSize),
- newPage = Math.max(Math.ceil(position / (displayItem.width / pages)), 1);
- store.loadPage(newPage);
- },
- /**
- * @private
- */
- parentOverrides: {
- /**
- * This method updates the information via the progress bar.
- * @private
- */
- updateInfo: function() {
- if (this.displayItem) {
- // eslint-disable-next-line vars-on-top
- var count = this.store.getCount(),
- pageData = this.getPageData(),
- message = count === 0 ? this.emptyMsg : Ext.String.format(this.displayMsg, pageData.fromRecord, pageData.toRecord, this.store.getTotalCount()),
- percentage = pageData.pageCount > 0 ? (pageData.currentPage / pageData.pageCount) : 0;
- this.displayItem.updateProgress(percentage, message, this.animate || this.defaultAnimConfig);
- }
- }
- }
- });
- /**
- * @deprecated 4.0.0 Ext.ux.RowExpander has been promoted to the core framework. Use
- * {@link Ext.grid.plugin.RowExpander} instead.
- *
- * Ext.ux.RowExpander is now just an empty stub that extends Ext.grid.plugin.RowExpander
- * for backward compatibility reasons.
- */
- Ext.define('Ext.ux.RowExpander', {
- extend: 'Ext.grid.plugin.RowExpander'
- });
- /**
- * Plugin for PagingToolbar which replaces the textfield input with a slider
- */
- Ext.define('Ext.ux.SlidingPager', {
- alias: 'plugin.ux-slidingpager',
- requires: [
- 'Ext.slider.Single',
- 'Ext.slider.Tip'
- ],
- /**
- * Creates new SlidingPager.
- * @param {Object} config Configuration options
- */
- constructor: function(config) {
- if (config) {
- Ext.apply(this, config);
- }
- },
- init: function(pbar) {
- var idx = pbar.items.indexOf(pbar.child("#inputItem")),
- slider;
- Ext.each(pbar.items.getRange(idx - 2, idx + 2), function(c) {
- c.hide();
- });
- slider = Ext.create('Ext.slider.Single', {
- width: 114,
- minValue: 1,
- maxValue: 1,
- hideLabel: true,
- tipText: function(thumb) {
- return Ext.String.format('Page <b>{0}</b> of <b>{1}</b>', thumb.value, thumb.slider.maxValue);
- },
- listeners: {
- changecomplete: function(s, v) {
- pbar.store.loadPage(v);
- }
- }
- });
- pbar.insert(idx + 1, slider);
- pbar.on({
- change: function(pb, data) {
- slider.setMaxValue(data.pageCount);
- slider.setValue(data.currentPage);
- }
- });
- }
- });
- /**
- * UX used to provide a spotlight around a specified component/element.
- */
- Ext.define('Ext.ux.Spotlight', {
- /**
- * @private
- * The baseCls for the spotlight elements
- */
- baseCls: 'x-spotlight',
- /**
- * @cfg animate {Boolean} True to animate the spotlight change
- * (defaults to true)
- */
- animate: true,
- /**
- * @cfg duration {Integer} The duration of the animation, in milliseconds
- * (defaults to 250)
- */
- duration: 250,
- /**
- * @cfg easing {String} The type of easing for the spotlight animatation
- * (defaults to null)
- */
- easing: null,
- /**
- * @private
- * True if the spotlight is active on the element
- */
- active: false,
- constructor: function(config) {
- Ext.apply(this, config);
- },
- /**
- * Create all the elements for the spotlight
- */
- createElements: function() {
- var me = this,
- baseCls = me.baseCls,
- body = Ext.getBody();
- me.right = body.createChild({
- cls: baseCls
- });
- me.left = body.createChild({
- cls: baseCls
- });
- me.top = body.createChild({
- cls: baseCls
- });
- me.bottom = body.createChild({
- cls: baseCls
- });
- me.all = Ext.create('Ext.CompositeElement', [
- me.right,
- me.left,
- me.top,
- me.bottom
- ]);
- },
- /**
- * Show the spotlight
- */
- show: function(el, callback, scope) {
- var me = this;
- // get the target element
- me.el = Ext.get(el);
- // create the elements if they don't already exist
- if (!me.right) {
- me.createElements();
- }
- if (!me.active) {
- // if the spotlight is not active, show it
- me.all.setDisplayed('');
- me.active = true;
- Ext.on('resize', me.syncSize, me);
- me.applyBounds(me.animate, false);
- } else {
- // if the spotlight is currently active, just move it
- me.applyBounds(false, false);
- }
- },
- /**
- * Hide the spotlight
- */
- hide: function(callback, scope) {
- var me = this;
- Ext.un('resize', me.syncSize, me);
- me.applyBounds(me.animate, true);
- },
- /**
- * Resizes the spotlight with the window size.
- */
- syncSize: function() {
- this.applyBounds(false, false);
- },
- /**
- * Resizes the spotlight depending on the arguments
- * @param {Boolean} animate True to animate the changing of the bounds
- * @param {Boolean} reverse True to reverse the animation
- */
- applyBounds: function(animate, reverse) {
- var me = this,
- box = me.el.getBox(),
- // get the current view width and height
- viewWidth = Ext.Element.getViewportWidth(),
- viewHeight = Ext.Element.getViewportHeight(),
- from, to, clone;
- // where the element should start (if animation)
- from = {
- right: {
- x: box.right,
- y: viewHeight,
- width: (viewWidth - box.right),
- height: 0
- },
- left: {
- x: 0,
- y: 0,
- width: box.x,
- height: 0
- },
- top: {
- x: viewWidth,
- y: 0,
- width: 0,
- height: box.y
- },
- bottom: {
- x: 0,
- y: (box.y + box.height),
- width: 0,
- height: (viewHeight - (box.y + box.height)) + 'px'
- }
- };
- // where the element needs to finish
- to = {
- right: {
- x: box.right,
- y: box.y,
- width: (viewWidth - box.right) + 'px',
- height: (viewHeight - box.y) + 'px'
- },
- left: {
- x: 0,
- y: 0,
- width: box.x + 'px',
- height: (box.y + box.height) + 'px'
- },
- top: {
- x: box.x,
- y: 0,
- width: (viewWidth - box.x) + 'px',
- height: box.y + 'px'
- },
- bottom: {
- x: 0,
- y: (box.y + box.height),
- width: (box.x + box.width) + 'px',
- height: (viewHeight - (box.y + box.height)) + 'px'
- }
- };
- // reverse the objects
- if (reverse) {
- clone = Ext.clone(from);
- from = to;
- to = clone;
- }
- if (animate) {
- Ext.Array.forEach([
- 'right',
- 'left',
- 'top',
- 'bottom'
- ], function(side) {
- me[side].setBox(from[side]);
- me[side].animate({
- duration: me.duration,
- easing: me.easing,
- to: to[side]
- });
- }, this);
- } else {
- Ext.Array.forEach([
- 'right',
- 'left',
- 'top',
- 'bottom'
- ], function(side) {
- me[side].setBox(Ext.apply(from[side], to[side]));
- me[side].repaint();
- }, this);
- }
- },
- /**
- * Removes all the elements for the spotlight
- */
- destroy: function() {
- var me = this;
- Ext.destroy(me.right, me.left, me.top, me.bottom);
- delete me.el;
- delete me.all;
- me.callParent();
- }
- });
- /**
- * Plugin for adding a close context menu to tabs. Note that the menu respects
- * the closable configuration on the tab. As such, commands like remove others
- * and remove all will not remove items that are not closable.
- */
- Ext.define('Ext.ux.TabCloseMenu', {
- extend: 'Ext.plugin.Abstract',
- alias: 'plugin.tabclosemenu',
- mixins: {
- observable: 'Ext.util.Observable'
- },
- /**
- * @cfg {String} closeTabText
- * The text for closing the current tab.
- */
- closeTabText: 'Close Tab',
- /**
- * @cfg {Boolean} showCloseOthers
- * Indicates whether to show the 'Close Others' option.
- */
- showCloseOthers: true,
- /**
- * @cfg {String} closeOtherTabsText
- * The text for closing all tabs except the current one.
- */
- closeOtherTabsText: 'Close Other Tabs',
- /**
- * @cfg {Boolean} showCloseAll
- * Indicates whether to show the 'Close All' option.
- */
- showCloseAll: true,
- /**
- * @cfg {String} closeAllTabsText
- * The text for closing all tabs.
- */
- closeAllTabsText: 'Close All Tabs',
- /**
- * @cfg {Array} extraItemsHead
- * An array of additional context menu items to add to the front of the context menu.
- */
- extraItemsHead: null,
- /**
- * @cfg {Array} extraItemsTail
- * An array of additional context menu items to add to the end of the context menu.
- */
- extraItemsTail: null,
- // public
- constructor: function(config) {
- this.callParent([
- config
- ]);
- this.mixins.observable.constructor.call(this, config);
- },
- init: function(tabpanel) {
- this.tabPanel = tabpanel;
- this.tabBar = tabpanel.down("tabbar");
- this.mon(this.tabPanel, {
- scope: this,
- afterlayout: this.onAfterLayout,
- single: true
- });
- },
- onAfterLayout: function() {
- this.mon(this.tabBar.el, {
- scope: this,
- contextmenu: this.onContextMenu,
- delegate: '.x-tab'
- });
- },
- destroy: function() {
- Ext.destroy(this.menu);
- this.callParent();
- },
- /**
- * @private
- */
- onContextMenu: function(event, target) {
- var me = this,
- menu = me.createMenu(),
- disableAll = true,
- disableOthers = true,
- tab = me.tabBar.getChildByElement(target),
- index = me.tabBar.items.indexOf(tab);
- me.item = me.tabPanel.getComponent(index);
- menu.child('#close').setDisabled(!me.item.closable);
- if (me.showCloseAll || me.showCloseOthers) {
- me.tabPanel.items.each(function(item) {
- if (item.closable) {
- disableAll = false;
- if (item !== me.item) {
- disableOthers = false;
- return false;
- }
- }
- return true;
- });
- if (me.showCloseAll) {
- menu.child('#closeAll').setDisabled(disableAll);
- }
- if (me.showCloseOthers) {
- menu.child('#closeOthers').setDisabled(disableOthers);
- }
- }
- event.preventDefault();
- me.fireEvent('beforemenu', menu, me.item, me);
- menu.showAt(event.getXY());
- },
- createMenu: function() {
- var me = this,
- items;
- if (!me.menu) {
- items = [
- {
- itemId: 'close',
- text: me.closeTabText,
- scope: me,
- handler: me.onClose
- }
- ];
- if (me.showCloseAll || me.showCloseOthers) {
- items.push('-');
- }
- if (me.showCloseOthers) {
- items.push({
- itemId: 'closeOthers',
- text: me.closeOtherTabsText,
- scope: me,
- handler: me.onCloseOthers
- });
- }
- if (me.showCloseAll) {
- items.push({
- itemId: 'closeAll',
- text: me.closeAllTabsText,
- scope: me,
- handler: me.onCloseAll
- });
- }
- if (me.extraItemsHead) {
- items = me.extraItemsHead.concat(items);
- }
- if (me.extraItemsTail) {
- items = items.concat(me.extraItemsTail);
- }
- me.menu = Ext.create('Ext.menu.Menu', {
- items: items,
- listeners: {
- hide: me.onHideMenu,
- scope: me
- }
- });
- }
- return me.menu;
- },
- onHideMenu: function() {
- var me = this;
- me.fireEvent('aftermenu', me.menu, me);
- },
- onClose: function() {
- this.tabPanel.remove(this.item);
- },
- onCloseOthers: function() {
- this.doClose(true);
- },
- onCloseAll: function() {
- this.doClose(false);
- },
- doClose: function(excludeActive) {
- var items = [];
- this.tabPanel.items.each(function(item) {
- if (item.closable) {
- if (!excludeActive || item !== this.item) {
- items.push(item);
- }
- }
- }, this);
- Ext.suspendLayouts();
- Ext.Array.forEach(items, function(item) {
- this.tabPanel.remove(item);
- }, this);
- Ext.resumeLayouts(true);
- }
- });
- /**
- * This plugin allow you to reorder tabs of a TabPanel.
- */
- Ext.define('Ext.ux.TabReorderer', {
- extend: 'Ext.ux.BoxReorderer',
- alias: 'plugin.tabreorderer',
- itemSelector: '.' + Ext.baseCSSPrefix + 'tab',
- init: function(tabPanel) {
- var me = this;
- me.callParent([
- tabPanel.getTabBar()
- ]);
- // Ensure reorderable property is copied into dynamically added tabs
- tabPanel.onAdd = Ext.Function.createSequence(tabPanel.onAdd, me.onAdd);
- },
- onBoxReady: function() {
- var tabs, tab, len,
- i = 0;
- this.callParent(arguments);
- // Copy reorderable property from card into tab
- for (tabs = this.container.items.items , len = tabs.length; i < len; i++) {
- tab = tabs[i];
- if (tab.card) {
- tab.reorderable = tab.card.reorderable;
- }
- }
- },
- onAdd: function(card, index) {
- card.tab.reorderable = card.reorderable;
- },
- afterBoxReflow: function() {
- var me = this;
- // Cannot use callParent, this is not called in the scope of this plugin,
- // but that of its Ext.dd.DD object
- Ext.ux.BoxReorderer.prototype.afterBoxReflow.apply(me, arguments);
- // Move the associated card to match the tab order
- if (me.dragCmp) {
- me.container.tabPanel.setActiveTab(me.dragCmp.card);
- me.container.tabPanel.move(me.dragCmp.card, me.curIndex);
- }
- }
- });
- Ext.ns('Ext.ux');
- /**
- * Plugin for adding a tab menu to a TabBar is the Tabs overflow.
- */
- Ext.define('Ext.ux.TabScrollerMenu', {
- alias: 'plugin.tabscrollermenu',
- requires: [
- 'Ext.menu.Menu'
- ],
- /**
- * @cfg {Number} pageSize How many items to allow per submenu.
- */
- pageSize: 10,
- /**
- * @cfg {Number} maxText How long should the title of each {@link Ext.menu.Item} be.
- */
- maxText: 15,
- /**
- * @cfg {String} menuPrefixText Text to prefix the submenus.
- */
- menuPrefixText: 'Items',
- /**
- * Creates new TabScrollerMenu.
- * @param {Object} config Configuration options
- */
- constructor: function(config) {
- Ext.apply(this, config);
- },
- /**
- * @private
- */
- init: function(tabPanel) {
- var me = this;
- me.tabPanel = tabPanel;
- tabPanel.on({
- render: function() {
- me.tabBar = tabPanel.tabBar;
- me.layout = me.tabBar.layout;
- me.layout.overflowHandler.handleOverflow = me.showButton.bind(me);
- me.layout.overflowHandler.clearOverflow = Ext.Function.createSequence(me.layout.overflowHandler.clearOverflow, me.hideButton, me);
- },
- destroy: me.destroy,
- scope: me,
- single: true
- });
- },
- showButton: function() {
- var me = this,
- result, button;
- result = Ext.getClass(me.layout.overflowHandler).prototype.handleOverflow.apply(me.layout.overflowHandler, arguments);
- button = me.menuButton;
- if (me.tabPanel.items.getCount() > 1) {
- if (!button) {
- button = me.menuButton = me.tabBar.body.createChild({
- cls: Ext.baseCSSPrefix + 'tab-tabmenu-right'
- }, me.tabBar.body.child('.' + Ext.baseCSSPrefix + 'box-scroller-right'));
- button.addClsOnOver(Ext.baseCSSPrefix + 'tab-tabmenu-over');
- button.on('click', me.showTabsMenu, me);
- }
- button.setVisibilityMode(Ext.dom.Element.DISPLAY);
- button.show();
- result.reservedSpace += button.getWidth();
- } else {
- me.hideButton();
- }
- return result;
- },
- hideButton: function() {
- var me = this;
- if (me.menuButton) {
- me.menuButton.hide();
- }
- },
- /**
- * Returns an the current page size (this.pageSize);
- * @return {Number} this.pageSize The current page size.
- */
- getPageSize: function() {
- return this.pageSize;
- },
- /**
- * Sets the number of menu items per submenu "page size".
- * @param {Number} pageSize The page size
- */
- setPageSize: function(pageSize) {
- this.pageSize = pageSize;
- },
- /**
- * Returns the current maxText length;
- * @return {Number} this.maxText The current max text length.
- */
- getMaxText: function() {
- return this.maxText;
- },
- /**
- * Sets the maximum text size for each menu item.
- * @param {Number} t The max text per each menu item.
- */
- setMaxText: function(t) {
- this.maxText = t;
- },
- /**
- * Returns the current menu prefix text String.;
- * @return {String} this.menuPrefixText The current menu prefix text.
- */
- getMenuPrefixText: function() {
- return this.menuPrefixText;
- },
- /**
- * Sets the menu prefix text String.
- * @param {String} t The menu prefix text.
- */
- setMenuPrefixText: function(t) {
- this.menuPrefixText = t;
- },
- showTabsMenu: function(e) {
- var me = this,
- target, xy;
- if (me.tabsMenu) {
- me.tabsMenu.removeAll();
- } else {
- me.tabsMenu = new Ext.menu.Menu();
- }
- me.generateTabMenuItems();
- target = Ext.get(e.getTarget());
- xy = target.getXY();
- // Y param + 24 pixels
- xy[1] += 24;
- me.tabsMenu.showAt(xy);
- },
- /**
- * @private
- */
- generateTabMenuItems: function() {
- var me = this,
- tabPanel = me.tabPanel,
- curActive = tabPanel.getActiveTab(),
- allItems = tabPanel.items.getRange(),
- pageSize = me.getPageSize(),
- tabsMenu = me.tabsMenu,
- totalItems, numSubMenus, remainder, i, curPage, menuItems, x, item, start, index;
- tabsMenu.suspendLayouts();
- allItems = Ext.Array.filter(allItems, function(item) {
- if (item.id === curActive.id) {
- return false;
- }
- return item.hidden ? !!item.hiddenByLayout : true;
- });
- totalItems = allItems.length;
- numSubMenus = Math.floor(totalItems / pageSize);
- remainder = totalItems % pageSize;
- if (totalItems > pageSize) {
- // Loop through all of the items and create submenus in chunks of 10
- for (i = 0; i < numSubMenus; i++) {
- curPage = (i + 1) * pageSize;
- menuItems = [];
- for (x = 0; x < pageSize; x++) {
- index = x + curPage - pageSize;
- item = allItems[index];
- menuItems.push(me.autoGenMenuItem(item));
- }
- tabsMenu.add({
- text: me.getMenuPrefixText() + ' ' + (curPage - pageSize + 1) + ' - ' + curPage,
- menu: menuItems
- });
- }
- // remaining items
- if (remainder > 0) {
- start = numSubMenus * pageSize;
- menuItems = [];
- for (i = start; i < totalItems; i++) {
- item = allItems[i];
- menuItems.push(me.autoGenMenuItem(item));
- }
- me.tabsMenu.add({
- text: me.menuPrefixText + ' ' + (start + 1) + ' - ' + (start + menuItems.length),
- menu: menuItems
- });
- }
- } else {
- for (i = 0; i < totalItems; ++i) {
- tabsMenu.add(me.autoGenMenuItem(allItems[i]));
- }
- }
- tabsMenu.resumeLayouts(true);
- },
- /**
- * @private
- */
- autoGenMenuItem: function(item) {
- var maxText = this.getMaxText(),
- text = Ext.util.Format.ellipsis(item.title, maxText);
- return {
- text: text,
- handler: this.showTabFromMenu,
- scope: this,
- disabled: item.disabled,
- tabToShow: item,
- iconCls: item.iconCls
- };
- },
- /**
- * @private
- */
- showTabFromMenu: function(menuItem) {
- this.tabPanel.setActiveTab(menuItem.tabToShow);
- },
- destroy: function() {
- Ext.destroy(this.tabsMenu, this.menuButton);
- this.callParent();
- }
- });
- /**
- * Plugin which allows items to be dropped onto a toolbar and be turned into new Toolbar items.
- * To use the plugin, you just need to provide a createItem implementation that takes the drop
- * data as an argument and returns an object that can be placed onto the toolbar. Example:
- * <pre>
- * Ext.create('Ext.ux.ToolbarDroppable', {
- * createItem: function(data) {
- * return Ext.create('Ext.Button', {text: data.text});
- * }
- * });
- * </pre>
- * The afterLayout function can also be overridden, and is called after a new item has been
- * created and inserted into the Toolbar. Use this for any logic that needs to be run after
- * the item has been created.
- */
- Ext.define('Ext.ux.ToolbarDroppable', {
- /**
- * Creates new ToolbarDroppable.
- * @param {Object} config Config options.
- */
- constructor: function(config) {
- Ext.apply(this, config);
- },
- /**
- * Initializes the plugin and saves a reference to the toolbar
- * @param {Ext.toolbar.Toolbar} toolbar The toolbar instance
- */
- init: function(toolbar) {
- /**
- * @property toolbar
- * @type Ext.toolbar.Toolbar
- * The toolbar instance that this plugin is tied to
- */
- this.toolbar = toolbar;
- this.toolbar.on({
- scope: this,
- render: this.createDropTarget
- });
- },
- /**
- * Creates a drop target on the toolbar
- */
- createDropTarget: function() {
- /**
- * @property dropTarget
- * @type Ext.dd.DropTarget
- * The drop target attached to the toolbar instance
- */
- this.dropTarget = Ext.create('Ext.dd.DropTarget', this.toolbar.getEl(), {
- notifyOver: this.notifyOver.bind(this),
- notifyDrop: this.notifyDrop.bind(this)
- });
- },
- /**
- * Adds the given DD Group to the drop target
- * @param {String} ddGroup The DD Group
- */
- addDDGroup: function(ddGroup) {
- this.dropTarget.addToGroup(ddGroup);
- },
- /**
- * Calculates the location on the toolbar to create the new sorter button based on the XY of the
- * drag event
- * @param {Ext.event.Event} e The event object
- * @return {Number} The index at which to insert the new button
- */
- calculateEntryIndex: function(e) {
- var entryIndex = 0,
- toolbar = this.toolbar,
- items = toolbar.items.items,
- count = items.length,
- xHover = e.getXY()[0],
- index = 0,
- el, xTotal, width, midpoint;
- for (; index < count; index++) {
- el = items[index].getEl();
- xTotal = el.getXY()[0];
- width = el.getWidth();
- midpoint = xTotal + width / 2;
- if (xHover < midpoint) {
- entryIndex = index;
- break;
- } else {
- entryIndex = index + 1;
- }
- }
- return entryIndex;
- },
- /**
- * Returns true if the drop is allowed on the drop target. This function can be overridden
- * and defaults to simply return true
- * @param {Object} data Arbitrary data from the drag source
- * @return {Boolean} True if the drop is allowed
- */
- canDrop: function(data) {
- return true;
- },
- /**
- * Custom notifyOver method which will be used in the plugin's internal DropTarget
- * @return {String} The CSS class to add
- */
- notifyOver: function(dragSource, event, data) {
- return this.canDrop.apply(this, arguments) ? this.dropTarget.dropAllowed : this.dropTarget.dropNotAllowed;
- },
- /**
- * Called when the drop has been made. Creates the new toolbar item, places it
- * at the correct location and calls the afterLayout callback.
- */
- notifyDrop: function(dragSource, event, data) {
- var canAdd = this.canDrop(dragSource, event, data),
- tbar = this.toolbar,
- entryIndex;
- if (canAdd) {
- entryIndex = this.calculateEntryIndex(event);
- tbar.insert(entryIndex, this.createItem(data));
- this.afterLayout();
- }
- return canAdd;
- },
- /**
- * Creates the new toolbar item based on drop data. This method must be implemented
- * by the plugin instance
- * @param {Object} data Arbitrary data from the drop
- * @return {Mixed} An item that can be added to a toolbar
- */
- createItem: function(data) {
- //<debug>
- Ext.raise("The createItem method must be implemented in the ToolbarDroppable plugin");
- },
- //</debug>
- /**
- * @method
- * Called after a new button has been created and added to the toolbar. Add any required
- * cleanup logic here
- */
- afterLayout: Ext.emptyFn
- });
- /**
- * A Picker field that contains a tree panel on its popup, enabling selection of tree nodes.
- */
- Ext.define('Ext.ux.TreePicker', {
- extend: 'Ext.form.field.Picker',
- xtype: 'treepicker',
- uses: [
- 'Ext.tree.Panel'
- ],
- triggerCls: Ext.baseCSSPrefix + 'form-arrow-trigger',
- config: {
- /**
- * @cfg {Ext.data.TreeStore} store
- * A tree store that the tree picker will be bound to
- */
- store: null,
- /**
- * @cfg {String} displayField
- * The field inside the model that will be used as the node's text.
- * Defaults to the default value of {@link Ext.tree.Panel}'s `displayField` configuration.
- */
- displayField: null,
- /**
- * @cfg {Array} columns
- * An optional array of columns for multi-column trees
- */
- columns: null,
- /**
- * @cfg {Boolean} selectOnTab
- * Whether the Tab key should select the currently highlighted item. Defaults to `true`.
- */
- selectOnTab: true,
- /**
- * @cfg {Number} maxPickerHeight
- * The maximum height of the tree dropdown. Defaults to 300.
- */
- maxPickerHeight: 300,
- /**
- * @cfg {Number} minPickerHeight
- * The minimum height of the tree dropdown. Defaults to 100.
- */
- minPickerHeight: 100
- },
- editable: false,
- /**
- * @event select
- * Fires when a tree node is selected
- * @param {Ext.ux.TreePicker} picker This tree picker
- * @param {Ext.data.Model} record The selected record
- */
- initComponent: function() {
- var me = this;
- me.callParent(arguments);
- me.mon(me.store, {
- scope: me,
- load: me.onLoad,
- update: me.onUpdate
- });
- },
- /**
- * Creates and returns the tree panel to be used as this field's picker.
- */
- createPicker: function() {
- var me = this,
- picker = new Ext.tree.Panel({
- baseCls: Ext.baseCSSPrefix + 'boundlist',
- shrinkWrapDock: 2,
- store: me.store,
- floating: true,
- displayField: me.displayField,
- columns: me.columns,
- minHeight: me.minPickerHeight,
- maxHeight: me.maxPickerHeight,
- manageHeight: false,
- shadow: false,
- listeners: {
- scope: me,
- itemclick: me.onItemClick,
- itemkeydown: me.onPickerKeyDown
- }
- }),
- view = picker.getView();
- if (Ext.isIE9 && Ext.isStrict) {
- // In IE9 strict mode, the tree view grows by the height of the horizontal scroll bar
- // when the items are highlighted or unhighlighted.
- // Also when items are collapsed or expanded the height of the view is off.
- // Forcing a repaint fixes the problem.
- view.on({
- scope: me,
- highlightitem: me.repaintPickerView,
- unhighlightitem: me.repaintPickerView,
- afteritemexpand: me.repaintPickerView,
- afteritemcollapse: me.repaintPickerView
- });
- }
- return picker;
- },
- /**
- * repaints the tree view
- */
- repaintPickerView: function() {
- var style = this.picker.getView().getEl().dom.style;
- // can't use Element.repaint because it contains a setTimeout, which results
- // in a flicker effect
- style.display = style.display;
- },
- // eslint-disable-line no-self-assign
- /**
- * Handles a click even on a tree node
- * @private
- * @param {Ext.tree.View} view
- * @param {Ext.data.Model} record
- * @param {HTMLElement} node
- * @param {Number} rowIndex
- * @param {Ext.event.Event} e
- */
- onItemClick: function(view, record, node, rowIndex, e) {
- this.selectItem(record);
- },
- /**
- * Handles a keypress event on the picker element
- * @private
- * @param {Ext.tree.View} treeView
- * @param {Ext.data.Model} record
- * @param {HTMLElement} item
- * @param {Number} index
- * @param {Ext.event.Event} e
- */
- onPickerKeyDown: function(treeView, record, item, index, e) {
- var key = e.getKey();
- if (key === e.ENTER || (key === e.TAB && this.selectOnTab)) {
- this.selectItem(record);
- }
- },
- /**
- * Changes the selection to a given record and closes the picker
- * @private
- * @param {Ext.data.Model} record
- */
- selectItem: function(record) {
- var me = this;
- me.setValue(record.getId());
- me.fireEvent('select', me, record);
- me.collapse();
- },
- /**
- * Runs when the picker is expanded. Selects the appropriate tree node based on the value
- * of the input element, and focuses the picker so that keyboard navigation will work.
- * @private
- */
- onExpand: function() {
- var picker = this.picker,
- store = picker.store,
- value = this.value,
- node;
- if (value) {
- node = store.getNodeById(value);
- }
- if (!node) {
- node = store.getRoot();
- }
- picker.ensureVisible(node, {
- select: true,
- focus: true
- });
- },
- /**
- * Sets the specified value into the field
- * @param {Mixed} value
- * @return {Ext.ux.TreePicker} this
- */
- setValue: function(value) {
- var me = this,
- record;
- me.value = value;
- if (me.store.loading) {
- // Called while the Store is loading. Ensure it is processed by the onLoad method.
- return me;
- }
- // try to find a record in the store that matches the value
- record = value ? me.store.getNodeById(value) : me.store.getRoot();
- if (value === undefined) {
- record = me.store.getRoot();
- me.value = record.getId();
- } else {
- record = me.store.getNodeById(value);
- }
- // set the raw value to the record's display field if a record was found
- me.setRawValue(record ? record.get(me.displayField) : '');
- return me;
- },
- getSubmitValue: function() {
- return this.value;
- },
- /**
- * Returns the current data value of the field (the idProperty of the record)
- * @return {Number}
- */
- getValue: function() {
- return this.value;
- },
- /**
- * Handles the store's load event.
- * @private
- */
- onLoad: function() {
- var value = this.value;
- if (value) {
- this.setValue(value);
- }
- },
- onUpdate: function(store, rec, type, modifiedFieldNames) {
- var display = this.displayField;
- if (type === 'edit' && modifiedFieldNames && Ext.Array.contains(modifiedFieldNames, display) && this.value === rec.getId()) {
- this.setRawValue(rec.get(display));
- }
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.ux.colorpick.Selection', {
- mixinId: 'colorselection',
- /* eslint-disable max-len */
- config: {
- /**
- * @cfg {"hex6"/"hex8"/"#hex6"/"#hex8"/"rgb"/"rgba"/"HEX6"/"HEX8"/"#HEX6"/"#HEX8"/"RGB"/"RGBA"} [format=hex6]
- * The color format to for the `value` config. The `value` can be set using any
- * supported format or named color, but the stored value will always be in this
- * format.
- *
- * Supported formats are:
- *
- * - hex6 - For example "ffaa00" (Note: does not preserve transparency).
- * - hex8 - For example "ffaa00ff" - the last 2 digits represent transparency
- * - #hex6 - For example "#ffaa00" (same as "hex6" but with a leading "#").
- * - #hex8 - For example "#ffaa00ff" (same as "hex8" but with a leading "#").
- * - rgb - For example "rgb(255,255,0)" (Note: does not preserve transparency).
- * - rgba - For example "rgba(255,255,0,.25)"
- * - HEX6 - Same as "hex6" but upper case.
- * - HEX8 - Same as "hex8" but upper case.
- * - #HEX6 - Same as "#hex6" but upper case.
- * - #HEX8 - Same as "#hex8" but upper case.
- * - RGB - Same as "rgb" but upper case.
- * - RGBA - Same as "rgba" but upper case.
- */
- format: 'hex6',
- /* eslint-enable max-len */
- /**
- * @cfg {String} [value=FF0000]
- * The initial color to highlight; see {@link #format} for supported formats.
- */
- value: 'FF0000',
- /**
- * @cfg {Object} color
- * This config property is used internally by the UI to maintain the full color.
- * Changes to this config are automatically reflected in `value` and vise-versa.
- * Setting `value` can, however, cause the alpha to be dropped if the new value
- * does not contain an alpha component.
- * @private
- */
- color: null,
- previousColor: null,
- /**
- * @cfg {String} [alphaDecimalFormat=#.##]
- * The format used by {@link Ext.util.Format#number} to format the alpha channel's
- * value.
- * @since 7.0.0
- */
- alphaDecimalFormat: '#.##'
- },
- applyColor: function(color) {
- var c = color;
- if (Ext.isString(c)) {
- c = Ext.ux.colorpick.ColorUtils.parseColor(color, this.getAlphaDecimalFormat());
- }
- return c;
- },
- applyFormat: function(format) {
- var formats = Ext.ux.colorpick.ColorUtils.formats;
- if (!formats.hasOwnProperty(format)) {
- //<debug>
- Ext.raise('The specified format "' + format + '" is invalid.');
- //</debug>
- return;
- }
- return format;
- },
- applyValue: function(color) {
- // Transform whatever incoming color we get to the proper format
- var c = Ext.ux.colorpick.ColorUtils.parseColor(color || '#000000', this.getAlphaDecimalFormat());
- return this.formatColor(c);
- },
- formatColor: function(color) {
- return Ext.ux.colorpick.ColorUtils.formats[this.getFormat()](color);
- },
- updateColor: function(color) {
- var me = this;
- // If the "color" is changed (via internal changes in the UI), update "value" as
- // well. Since these are always tracking each other, we guard against the case
- // where we are being updated *because* "value" is being set.
- if (!me.syncing) {
- me.syncing = true;
- me.setValue(me.formatColor(color));
- me.syncing = false;
- }
- },
- updateValue: function(value, oldValue) {
- var me = this;
- // If the "value" is changed, update "color" as well. Since these are always
- // tracking each other, we guard against the case where we are being updated
- // *because* "color" is being set.
- if (!me.syncing) {
- me.syncing = true;
- me.setColor(value);
- me.syncing = false;
- }
- this.fireEvent('change', me, value, oldValue);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.ux.colorpick.ColorUtils', function(ColorUtils) {
- var oldIE = Ext.isIE && Ext.ieVersion < 10;
- return {
- singleton: true,
- constructor: function() {
- ColorUtils = this;
- },
- backgroundTpl: oldIE ? 'filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, ' + 'startColorstr=\'#{alpha}{hex}\', endColorstr=\'#{alpha}{hex}\');' : 'background: {rgba};',
- setBackground: oldIE ? function(el, color) {
- var tpl, data, bgStyle;
- if (el) {
- tpl = Ext.XTemplate.getTpl(ColorUtils, 'backgroundTpl');
- data = {
- hex: ColorUtils.rgb2hex(color.r, color.g, color.b),
- alpha: Math.floor(color.a * 255).toString(16)
- };
- bgStyle = tpl.apply(data);
- el.applyStyles(bgStyle);
- }
- } : function(el, color) {
- var tpl, data, bgStyle;
- if (el) {
- tpl = Ext.XTemplate.getTpl(ColorUtils, 'backgroundTpl');
- data = {
- rgba: ColorUtils.getRGBAString(color)
- };
- bgStyle = tpl.apply(data);
- el.applyStyles(bgStyle);
- }
- },
- // parse and format functions under objects that match supported format config
- // values of the color picker; parse() methods receive the supplied color value
- // as a string (i.e "FFAAAA") and return an object form, just like the one
- // ColorPickerModel vm "selectedColor" uses. That same object form is used as a
- // parameter to the format() methods, where the appropriate string form is expected
- // for the return result
- formats: {
- // "RGB(100,100,100)"
- RGB: function(colorO) {
- return ColorUtils.getRGBString(colorO).toUpperCase();
- },
- // "RGBA(100,100,100,0.5)"
- RGBA: function(colorO) {
- return ColorUtils.getRGBAString(colorO).toUpperCase();
- },
- // "FFAA00"
- HEX6: function(colorO) {
- return ColorUtils.rgb2hex(colorO.r, colorO.g, colorO.b);
- },
- // "FFAA00FF" (last 2 are opacity)
- HEX8: function(colorO) {
- var hex = ColorUtils.rgb2hex(colorO.r, colorO.g, colorO.b),
- opacityHex = Math.round(colorO.a * 255).toString(16);
- if (opacityHex.length < 2) {
- hex += '0';
- }
- hex += opacityHex.toUpperCase();
- return hex;
- }
- },
- /* eslint-disable no-useless-escape */
- hexRe: /^#?(([0-9a-f]{8})|((?:[0-9a-f]{3}){1,2}))$/i,
- rgbaAltRe: /^rgba\(\s*([\w#\d]+)\s*,\s*([\d\.]+)\s*\)$/i,
- rgbaRe: /^rgba\(\s*([\d\.]+)\s*,\s*([\d\.]+)\s*,\s*([\d\.]+)\s*,\s*([\d\.]+)\s*\)$/i,
- rgbRe: /^rgb\(\s*([\d\.]+)\s*,\s*([\d\.]+)\s*,\s*([\d\.]+)\s*\)$/i,
- /* eslint-enable no-useless-escape */
- /**
- * Turn a string to a color object. Supports these formats:
- *
- * - "#ABC" (HEX short)
- * - "#ABCDEF" (HEX)
- * - "#ABCDEFDD" (HEX with opacity)
- * - "red" (named colors - see
- * [Web Colors](http://en.wikipedia.org/wiki/Web_colors) for a full list)
- * - "rgba(r,g,b,a)" i.e "rgba(255,0,0,1)" (a == alpha == 0-1)
- * - "rgba(red, 0.4)"
- * - "rgba(#ABC, 0.9)"
- * - "rgba(#ABCDEF, 0.8)"
- *
- * @param {String} color The color string to parse.
- * @param {String} alphaFormat The format of decimal places for the Alpha channel.
- * @return {Object} Object with various color properties.
- * @return {Number} return.r The red component (0-255).
- * @return {Number} return.g The green component (0-255).
- * @return {Number} return.b The blue component (0-255).
- * @return {Number} return.a The red component (0-1).
- * @return {Number} return.h The hue component (0-1).
- * @return {Number} return.s The saturation component (0-1).
- * @return {Number} return.v The value component (0-1).
- */
- parseColor: function(color, alphaFormat) {
- if (!color) {
- return null;
- }
- // eslint-disable-next-line vars-on-top
- var me = this,
- rgb = me.colorMap[color],
- match, ret, hsv;
- if (rgb) {
- ret = {
- r: rgb[0],
- g: rgb[1],
- b: rgb[2],
- a: 1
- };
- } else if (color === 'transparent') {
- ret = {
- r: 0,
- g: 0,
- b: 0,
- a: 0
- };
- } else {
- match = me.hexRe.exec(color);
- if (match) {
- match = match[1];
- // the captured hex
- switch (match.length) {
- default:
- return null;
- case 3:
- ret = {
- // double the number (e.g. 6 - > 66, a -> aa) and convert to decimal
- r: parseInt(match[0] + match[0], 16),
- g: parseInt(match[1] + match[1], 16),
- b: parseInt(match[2] + match[2], 16),
- a: 1
- };
- break;
- case 6:
- case 8:
- ret = {
- r: parseInt(match.substr(0, 2), 16),
- g: parseInt(match.substr(2, 2), 16),
- b: parseInt(match.substr(4, 2), 16),
- a: parseInt(match.substr(6, 2) || 'ff', 16) / 255
- };
- break;
- }
- } else {
- match = me.rgbaRe.exec(color);
- if (match) {
- // proper css => rgba(r,g,b,a)
- ret = {
- r: parseFloat(match[1]),
- g: parseFloat(match[2]),
- b: parseFloat(match[3]),
- a: parseFloat(match[4])
- };
- } else {
- match = me.rgbaAltRe.exec(color);
- if (match) {
- // scss shorthands = rgba(red, 0.4), rgba(#222, 0.9), rgba(#444433, 0.8)
- ret = me.parseColor(match[1]);
- // we have HSV filled in, so poke on "a" and we're done
- ret.a = parseFloat(match[2]);
- return ret;
- }
- match = me.rgbRe.exec(color);
- if (match) {
- ret = {
- r: parseFloat(match[1]),
- g: parseFloat(match[2]),
- b: parseFloat(match[3]),
- a: 1
- };
- } else {
- return null;
- }
- }
- }
- }
- // format alpha channel
- if (alphaFormat) {
- ret.a = Ext.util.Format.number(ret.a, alphaFormat);
- }
- hsv = this.rgb2hsv(ret.r, ret.g, ret.b);
- return Ext.apply(ret, hsv);
- },
- isValid: function(color) {
- return ColorUtils.parseColor(color) !== null;
- },
- /**
- *
- * @param rgba
- * @return {String}
- */
- getRGBAString: function(rgba) {
- return "rgba(" + rgba.r + "," + rgba.g + "," + rgba.b + "," + rgba.a + ")";
- },
- /**
- * Returns a rgb css string whith this color (without the alpha channel)
- * @param rgb
- * @return {String}
- */
- getRGBString: function(rgb) {
- return "rgb(" + rgb.r + "," + rgb.g + "," + rgb.b + ")";
- },
- /**
- * Following standard math to convert from hsl to rgb
- * Check out wikipedia page for more information on how this works
- * h => [0,1]
- * s,l => [0,1]
- * @param h
- * @param s
- * @param v
- * @return {Object} An object with "r", "g" and "b" color properties.
- */
- hsv2rgb: function(h, s, v) {
- h = h * 360;
- if (h === 360) {
- h = 0;
- }
- // eslint-disable-next-line vars-on-top
- var c = v * s,
- hprime = h / 60,
- x = c * (1 - Math.abs(hprime % 2 - 1)),
- rgb = [
- 0,
- 0,
- 0
- ],
- m;
- switch (Math.floor(hprime)) {
- case 0:
- rgb = [
- c,
- x,
- 0
- ];
- break;
- case 1:
- rgb = [
- x,
- c,
- 0
- ];
- break;
- case 2:
- rgb = [
- 0,
- c,
- x
- ];
- break;
- case 3:
- rgb = [
- 0,
- x,
- c
- ];
- break;
- case 4:
- rgb = [
- x,
- 0,
- c
- ];
- break;
- case 5:
- rgb = [
- c,
- 0,
- x
- ];
- break;
- default:
- //<debug>
- console.error("unknown color " + h + ' ' + s + " " + v);
- //</debug>
- break;
- }
- m = v - c;
- rgb[0] += m;
- rgb[1] += m;
- rgb[2] += m;
- rgb[0] = Math.round(rgb[0] * 255);
- rgb[1] = Math.round(rgb[1] * 255);
- rgb[2] = Math.round(rgb[2] * 255);
- return {
- r: rgb[0],
- g: rgb[1],
- b: rgb[2]
- };
- },
- /**
- * http://en.wikipedia.org/wiki/HSL_and_HSV
- * @param {Number} r The red component (0-255).
- * @param {Number} g The green component (0-255).
- * @param {Number} b The blue component (0-255).
- * @return {Object} An object with "h", "s" and "v" color properties.
- */
- rgb2hsv: function(r, g, b) {
- r = r / 255;
- g = g / 255;
- b = b / 255;
- // eslint-disable-next-line vars-on-top
- var M = Math.max(r, g, b),
- m = Math.min(r, g, b),
- c = M - m,
- hprime = 0,
- s = 0,
- h, v;
- if (c !== 0) {
- if (M === r) {
- hprime = ((g - b) / c) % 6;
- } else if (M === g) {
- hprime = ((b - r) / c) + 2;
- } else if (M === b) {
- hprime = ((r - g) / c) + 4;
- }
- }
- h = hprime * 60;
- if (h === 360) {
- h = 0;
- }
- v = M;
- if (c !== 0) {
- s = c / v;
- }
- h = h / 360;
- if (h < 0) {
- h = h + 1;
- }
- return {
- h: h,
- s: s,
- v: v
- };
- },
- /**
- *
- * @param r
- * @param g
- * @param b
- * @return {String}
- */
- rgb2hex: function(r, g, b) {
- r = r.toString(16);
- g = g.toString(16);
- b = b.toString(16);
- if (r.length < 2) {
- r = '0' + r;
- }
- if (g.length < 2) {
- g = '0' + g;
- }
- if (b.length < 2) {
- b = '0' + b;
- }
- return (r + g + b).toUpperCase();
- },
- colorMap: {
- aliceblue: [
- 240,
- 248,
- 255
- ],
- antiquewhite: [
- 250,
- 235,
- 215
- ],
- aqua: [
- 0,
- 255,
- 255
- ],
- aquamarine: [
- 127,
- 255,
- 212
- ],
- azure: [
- 240,
- 255,
- 255
- ],
- beige: [
- 245,
- 245,
- 220
- ],
- bisque: [
- 255,
- 228,
- 196
- ],
- black: [
- 0,
- 0,
- 0
- ],
- blanchedalmond: [
- 255,
- 235,
- 205
- ],
- blue: [
- 0,
- 0,
- 255
- ],
- blueviolet: [
- 138,
- 43,
- 226
- ],
- brown: [
- 165,
- 42,
- 42
- ],
- burlywood: [
- 222,
- 184,
- 135
- ],
- cadetblue: [
- 95,
- 158,
- 160
- ],
- chartreuse: [
- 127,
- 255,
- 0
- ],
- chocolate: [
- 210,
- 105,
- 30
- ],
- coral: [
- 255,
- 127,
- 80
- ],
- cornflowerblue: [
- 100,
- 149,
- 237
- ],
- cornsilk: [
- 255,
- 248,
- 220
- ],
- crimson: [
- 220,
- 20,
- 60
- ],
- cyan: [
- 0,
- 255,
- 255
- ],
- darkblue: [
- 0,
- 0,
- 139
- ],
- darkcyan: [
- 0,
- 139,
- 139
- ],
- darkgoldenrod: [
- 184,
- 132,
- 11
- ],
- darkgray: [
- 169,
- 169,
- 169
- ],
- darkgreen: [
- 0,
- 100,
- 0
- ],
- darkgrey: [
- 169,
- 169,
- 169
- ],
- darkkhaki: [
- 189,
- 183,
- 107
- ],
- darkmagenta: [
- 139,
- 0,
- 139
- ],
- darkolivegreen: [
- 85,
- 107,
- 47
- ],
- darkorange: [
- 255,
- 140,
- 0
- ],
- darkorchid: [
- 153,
- 50,
- 204
- ],
- darkred: [
- 139,
- 0,
- 0
- ],
- darksalmon: [
- 233,
- 150,
- 122
- ],
- darkseagreen: [
- 143,
- 188,
- 143
- ],
- darkslateblue: [
- 72,
- 61,
- 139
- ],
- darkslategray: [
- 47,
- 79,
- 79
- ],
- darkslategrey: [
- 47,
- 79,
- 79
- ],
- darkturquoise: [
- 0,
- 206,
- 209
- ],
- darkviolet: [
- 148,
- 0,
- 211
- ],
- deeppink: [
- 255,
- 20,
- 147
- ],
- deepskyblue: [
- 0,
- 191,
- 255
- ],
- dimgray: [
- 105,
- 105,
- 105
- ],
- dimgrey: [
- 105,
- 105,
- 105
- ],
- dodgerblue: [
- 30,
- 144,
- 255
- ],
- firebrick: [
- 178,
- 34,
- 34
- ],
- floralwhite: [
- 255,
- 255,
- 240
- ],
- forestgreen: [
- 34,
- 139,
- 34
- ],
- fuchsia: [
- 255,
- 0,
- 255
- ],
- gainsboro: [
- 220,
- 220,
- 220
- ],
- ghostwhite: [
- 248,
- 248,
- 255
- ],
- gold: [
- 255,
- 215,
- 0
- ],
- goldenrod: [
- 218,
- 165,
- 32
- ],
- gray: [
- 128,
- 128,
- 128
- ],
- green: [
- 0,
- 128,
- 0
- ],
- greenyellow: [
- 173,
- 255,
- 47
- ],
- grey: [
- 128,
- 128,
- 128
- ],
- honeydew: [
- 240,
- 255,
- 240
- ],
- hotpink: [
- 255,
- 105,
- 180
- ],
- indianred: [
- 205,
- 92,
- 92
- ],
- indigo: [
- 75,
- 0,
- 130
- ],
- ivory: [
- 255,
- 255,
- 240
- ],
- khaki: [
- 240,
- 230,
- 140
- ],
- lavender: [
- 230,
- 230,
- 250
- ],
- lavenderblush: [
- 255,
- 240,
- 245
- ],
- lawngreen: [
- 124,
- 252,
- 0
- ],
- lemonchiffon: [
- 255,
- 250,
- 205
- ],
- lightblue: [
- 173,
- 216,
- 230
- ],
- lightcoral: [
- 240,
- 128,
- 128
- ],
- lightcyan: [
- 224,
- 255,
- 255
- ],
- lightgoldenrodyellow: [
- 250,
- 250,
- 210
- ],
- lightgray: [
- 211,
- 211,
- 211
- ],
- lightgreen: [
- 144,
- 238,
- 144
- ],
- lightgrey: [
- 211,
- 211,
- 211
- ],
- lightpink: [
- 255,
- 182,
- 193
- ],
- lightsalmon: [
- 255,
- 160,
- 122
- ],
- lightseagreen: [
- 32,
- 178,
- 170
- ],
- lightskyblue: [
- 135,
- 206,
- 250
- ],
- lightslategray: [
- 119,
- 136,
- 153
- ],
- lightslategrey: [
- 119,
- 136,
- 153
- ],
- lightsteelblue: [
- 176,
- 196,
- 222
- ],
- lightyellow: [
- 255,
- 255,
- 224
- ],
- lime: [
- 0,
- 255,
- 0
- ],
- limegreen: [
- 50,
- 205,
- 50
- ],
- linen: [
- 250,
- 240,
- 230
- ],
- magenta: [
- 255,
- 0,
- 255
- ],
- maroon: [
- 128,
- 0,
- 0
- ],
- mediumaquamarine: [
- 102,
- 205,
- 170
- ],
- mediumblue: [
- 0,
- 0,
- 205
- ],
- mediumorchid: [
- 186,
- 85,
- 211
- ],
- mediumpurple: [
- 147,
- 112,
- 219
- ],
- mediumseagreen: [
- 60,
- 179,
- 113
- ],
- mediumslateblue: [
- 123,
- 104,
- 238
- ],
- mediumspringgreen: [
- 0,
- 250,
- 154
- ],
- mediumturquoise: [
- 72,
- 209,
- 204
- ],
- mediumvioletred: [
- 199,
- 21,
- 133
- ],
- midnightblue: [
- 25,
- 25,
- 112
- ],
- mintcream: [
- 245,
- 255,
- 250
- ],
- mistyrose: [
- 255,
- 228,
- 225
- ],
- moccasin: [
- 255,
- 228,
- 181
- ],
- navajowhite: [
- 255,
- 222,
- 173
- ],
- navy: [
- 0,
- 0,
- 128
- ],
- oldlace: [
- 253,
- 245,
- 230
- ],
- olive: [
- 128,
- 128,
- 0
- ],
- olivedrab: [
- 107,
- 142,
- 35
- ],
- orange: [
- 255,
- 165,
- 0
- ],
- orangered: [
- 255,
- 69,
- 0
- ],
- orchid: [
- 218,
- 112,
- 214
- ],
- palegoldenrod: [
- 238,
- 232,
- 170
- ],
- palegreen: [
- 152,
- 251,
- 152
- ],
- paleturquoise: [
- 175,
- 238,
- 238
- ],
- palevioletred: [
- 219,
- 112,
- 147
- ],
- papayawhip: [
- 255,
- 239,
- 213
- ],
- peachpuff: [
- 255,
- 218,
- 185
- ],
- peru: [
- 205,
- 133,
- 63
- ],
- pink: [
- 255,
- 192,
- 203
- ],
- plum: [
- 221,
- 160,
- 203
- ],
- powderblue: [
- 176,
- 224,
- 230
- ],
- purple: [
- 128,
- 0,
- 128
- ],
- red: [
- 255,
- 0,
- 0
- ],
- rosybrown: [
- 188,
- 143,
- 143
- ],
- royalblue: [
- 65,
- 105,
- 225
- ],
- saddlebrown: [
- 139,
- 69,
- 19
- ],
- salmon: [
- 250,
- 128,
- 114
- ],
- sandybrown: [
- 244,
- 164,
- 96
- ],
- seagreen: [
- 46,
- 139,
- 87
- ],
- seashell: [
- 255,
- 245,
- 238
- ],
- sienna: [
- 160,
- 82,
- 45
- ],
- silver: [
- 192,
- 192,
- 192
- ],
- skyblue: [
- 135,
- 206,
- 235
- ],
- slateblue: [
- 106,
- 90,
- 205
- ],
- slategray: [
- 119,
- 128,
- 144
- ],
- slategrey: [
- 119,
- 128,
- 144
- ],
- snow: [
- 255,
- 255,
- 250
- ],
- springgreen: [
- 0,
- 255,
- 127
- ],
- steelblue: [
- 70,
- 130,
- 180
- ],
- tan: [
- 210,
- 180,
- 140
- ],
- teal: [
- 0,
- 128,
- 128
- ],
- thistle: [
- 216,
- 191,
- 216
- ],
- tomato: [
- 255,
- 99,
- 71
- ],
- turquoise: [
- 64,
- 224,
- 208
- ],
- violet: [
- 238,
- 130,
- 238
- ],
- wheat: [
- 245,
- 222,
- 179
- ],
- white: [
- 255,
- 255,
- 255
- ],
- whitesmoke: [
- 245,
- 245,
- 245
- ],
- yellow: [
- 255,
- 255,
- 0
- ],
- yellowgreen: [
- 154,
- 205,
- 5
- ]
- }
- };
- }, function(ColorUtils) {
- var formats = ColorUtils.formats,
- lowerized = {};
- formats['#HEX6'] = function(color) {
- return '#' + formats.HEX6(color);
- };
- formats['#HEX8'] = function(color) {
- return '#' + formats.HEX8(color);
- };
- Ext.Object.each(formats, function(name, fn) {
- lowerized[name.toLowerCase()] = function(color) {
- var ret = fn(color);
- return ret.toLowerCase();
- };
- });
- Ext.apply(formats, lowerized);
- });
- /**
- * @private
- */
- Ext.define('Ext.ux.colorpick.ColorMapController', {
- extend: 'Ext.app.ViewController',
- alias: 'controller.colorpickercolormapcontroller',
- requires: [
- 'Ext.ux.colorpick.ColorUtils'
- ],
- // After the component is rendered
- onFirstBoxReady: function() {
- var me = this,
- colorMap = me.getView(),
- dragHandle = colorMap.down('#dragHandle'),
- dd = dragHandle.dd;
- // configure draggable constraints
- dd.constrain = true;
- dd.constrainTo = colorMap.getEl();
- dd.initialConstrainTo = dd.constrainTo;
- // needed otheriwse error EXTJS-13187
- // event handlers
- dd.on('drag', Ext.bind(me.onHandleDrag, me));
- me.mon(colorMap.getEl(), {
- mousedown: me.onMouseDown,
- dragstart: me.onDragStart,
- scope: me
- });
- },
- // Fires when handle is dragged; propagates "handledrag" event on the ColorMap
- // with parameters "percentX" and "percentY", both 0-1, representing the handle
- // position on the color map, relative to the container
- onHandleDrag: function(componentDragger, e) {
- var me = this,
- container = me.getView(),
- // the Color Map
- dragHandle = container.down('#dragHandle'),
- x = dragHandle.getX() - container.getX(),
- y = dragHandle.getY() - container.getY(),
- containerEl = container.getEl(),
- containerWidth = containerEl.getWidth(),
- containerHeight = containerEl.getHeight(),
- xRatio = x / containerWidth,
- yRatio = y / containerHeight;
- // Adjust x/y ratios for dragger always being 1 pixel from the edge on the right
- if (xRatio > 0.99) {
- xRatio = 1;
- }
- if (yRatio > 0.99) {
- yRatio = 1;
- }
- container.fireEvent('handledrag', xRatio, yRatio);
- },
- // Whenever we mousedown over the colormap area
- onMouseDown: function(e) {
- var me = this,
- container = me.getView(),
- dragHandle = container.down('#dragHandle');
- // position drag handle accordingly
- dragHandle.setY(e.getY());
- dragHandle.setX(e.getX());
- me.onHandleDrag();
- // tie into the default dd mechanism
- dragHandle.dd.onMouseDown(e, dragHandle.dd.el);
- },
- // Whenever we start a drag over the colormap area
- onDragStart: function(e) {
- var me = this,
- container = me.getView(),
- dragHandle = container.down('#dragHandle');
- // tie into the default dd mechanism
- dragHandle.dd.onDragStart(e, dragHandle.dd.el);
- },
- // Whenever the map is clicked (but not the drag handle) we need to position
- // the drag handle to the point of click
- onMapClick: function(e) {
- var me = this,
- container = me.getView(),
- // the Color Map
- dragHandle = container.down('#dragHandle'),
- cXY = container.getXY(),
- eXY = e.getXY(),
- left, top;
- left = eXY[0] - cXY[0];
- top = eXY[1] - cXY[1];
- dragHandle.getEl().setStyle({
- left: left + 'px',
- top: top + 'px'
- });
- me.onHandleDrag();
- },
- // Whenever the underlying binding data is changed we need to
- // update position of the dragger; active drag state has been
- // accounted for earlier
- onColorBindingChanged: function(selectedColor) {
- var me = this,
- vm = me.getViewModel(),
- rgba = vm.get('selectedColor'),
- container = me.getView(),
- // the Color Map
- dragHandle = container.down('#dragHandle'),
- containerEl = container.getEl(),
- containerWidth = containerEl.getWidth(),
- containerHeight = containerEl.getHeight(),
- hsv, xRatio, yRatio, left, top;
- // Color map selection really only depends on saturation and value of the color
- hsv = Ext.ux.colorpick.ColorUtils.rgb2hsv(rgba.r, rgba.g, rgba.b);
- // x-axis of color map with value 0-1 translates to saturation
- xRatio = hsv.s;
- left = containerWidth * xRatio;
- // y-axis of color map with value 0-1 translates to reverse of "value"
- yRatio = 1 - hsv.v;
- top = containerHeight * yRatio;
- // Position dragger
- dragHandle.getEl().setStyle({
- left: left + 'px',
- top: top + 'px'
- });
- },
- // Whenever only Hue changes we can update the
- // background color of the color map
- // Param "hue" has value of 0-1
- onHueBindingChanged: function(hue) {
- var me = this,
- fullColorRGB, hex;
- fullColorRGB = Ext.ux.colorpick.ColorUtils.hsv2rgb(hue, 1, 1);
- hex = Ext.ux.colorpick.ColorUtils.rgb2hex(fullColorRGB.r, fullColorRGB.g, fullColorRGB.b);
- me.getView().getEl().applyStyles({
- 'background-color': '#' + hex
- });
- }
- });
- /**
- * The main colorful square for selecting color shades by dragging around the
- * little circle.
- * @private
- */
- Ext.define('Ext.ux.colorpick.ColorMap', {
- extend: 'Ext.container.Container',
- alias: 'widget.colorpickercolormap',
- controller: 'colorpickercolormapcontroller',
- requires: [
- 'Ext.ux.colorpick.ColorMapController'
- ],
- cls: Ext.baseCSSPrefix + 'colorpicker-colormap',
- // This is the drag "circle"; note it's 1x1 in size to allow full
- // travel around the color map; the inner div has the bigger image
- items: [
- {
- xtype: 'component',
- cls: Ext.baseCSSPrefix + 'colorpicker-colormap-draghandle-container',
- itemId: 'dragHandle',
- width: 1,
- height: 1,
- draggable: true,
- html: '<div class="' + Ext.baseCSSPrefix + 'colorpicker-colormap-draghandle"></div>'
- }
- ],
- listeners: {
- boxready: {
- single: true,
- fn: 'onFirstBoxReady',
- scope: 'controller'
- },
- colorbindingchanged: {
- fn: 'onColorBindingChanged',
- scope: 'controller'
- },
- huebindingchanged: {
- fn: 'onHueBindingChanged',
- scope: 'controller'
- }
- },
- afterRender: function() {
- var me = this,
- src = me.mapGradientUrl,
- el = me.el;
- me.callParent();
- if (!src) {
- // We do this trick to allow the Sass to calculate resource image path for
- // our package and pick up the proper image URL here.
- src = el.getStyle('background-image');
- src = src.substring(4, src.length - 1);
- // strip off outer "url(...)"
- // In IE8 this path will have quotes around it
- if (src.indexOf('"') === 0) {
- src = src.substring(1, src.length - 1);
- }
- // Then remember it on our prototype for any subsequent instances.
- Ext.ux.colorpick.ColorMap.prototype.mapGradientUrl = src;
- }
- // Now clear that style because it will conflict with the background-color
- el.setStyle('background-image', 'none');
- // Create the image with transparent PNG with black and white gradient shades;
- // it blends with the background color (which changes with hue selection). This
- // must be an IMG in order to properly stretch to fit.
- el = me.layout.getElementTarget();
- // the el for items and html
- el.createChild({
- tag: 'img',
- cls: Ext.baseCSSPrefix + 'colorpicker-colormap-blender',
- src: src
- });
- },
- // Called via data binding whenever selectedColor changes; fires "colorbindingchanged"
- setPosition: function(data) {
- var me = this,
- dragHandle = me.down('#dragHandle');
- // Too early in the render cycle? Skip event
- if (!dragHandle.dd || !dragHandle.dd.constrain) {
- return;
- }
- // User actively dragging? Skip event
- if (typeof dragHandle.dd.dragEnded !== 'undefined' && !dragHandle.dd.dragEnded) {
- return;
- }
- me.fireEvent('colorbindingchanged', data);
- },
- // Called via data binding whenever selectedColor.h changes; fires "huebindingchanged" event
- setHue: function(hue) {
- var me = this;
- // Too early in the render cycle? Skip event
- if (!me.getEl()) {
- return;
- }
- me.fireEvent('huebindingchanged', hue);
- }
- });
- /**
- * View Model that holds the "selectedColor" of the color picker container.
- */
- Ext.define('Ext.ux.colorpick.SelectorModel', {
- extend: 'Ext.app.ViewModel',
- alias: 'viewmodel.colorpick-selectormodel',
- requires: [
- 'Ext.ux.colorpick.ColorUtils'
- ],
- data: {
- selectedColor: {
- r: 255,
- // red
- g: 255,
- // green
- b: 255,
- // blue
- h: 0,
- // hue,
- s: 1,
- // saturation
- v: 1,
- // value
- a: 1
- },
- // alpha (opacity)
- previousColor: {
- r: 0,
- // red
- g: 0,
- // green
- b: 0,
- // blue
- h: 0,
- // hue,
- s: 1,
- // saturation
- v: 1,
- // value
- a: 1
- }
- },
- // alpha (opacity)
- formulas: {
- // Hexadecimal representation of the color
- hex: {
- get: function(get) {
- var r = get('selectedColor.r').toString(16),
- g = get('selectedColor.g').toString(16),
- b = get('selectedColor.b').toString(16),
- result;
- result = Ext.ux.colorpick.ColorUtils.rgb2hex(r, g, b);
- return '#' + result;
- },
- set: function(hex) {
- var rgb = Ext.ux.colorpick.ColorUtils.parseColor(hex);
- this.changeRGB(rgb);
- }
- },
- // "R" in "RGB"
- red: {
- get: function(get) {
- return get('selectedColor.r');
- },
- set: function(r) {
- this.changeRGB({
- r: r
- });
- }
- },
- // "G" in "RGB"
- green: {
- get: function(get) {
- return get('selectedColor.g');
- },
- set: function(g) {
- this.changeRGB({
- g: g
- });
- }
- },
- // "B" in "RGB"
- blue: {
- get: function(get) {
- return get('selectedColor.b');
- },
- set: function(b) {
- this.changeRGB({
- b: b
- });
- }
- },
- // "H" in HSV
- hue: {
- get: function(get) {
- return get('selectedColor.h') * 360;
- },
- set: function(hue) {
- this.changeHSV({
- h: hue / 360
- });
- }
- },
- // "S" in HSV
- saturation: {
- get: function(get) {
- return get('selectedColor.s') * 100;
- },
- set: function(saturation) {
- this.changeHSV({
- s: saturation / 100
- });
- }
- },
- // "V" in HSV
- value: {
- get: function(get) {
- var v = get('selectedColor.v');
- return v * 100;
- },
- set: function(value) {
- this.changeHSV({
- v: value / 100
- });
- }
- },
- alpha: {
- get: function(data) {
- var a = data('selectedColor.a');
- return a * 100;
- },
- set: function(alpha) {
- this.set('selectedColor', Ext.applyIf({
- a: alpha / 100
- }, this.data.selectedColor));
- }
- }
- },
- // formulas
- changeHSV: function(hsv) {
- var rgb;
- Ext.applyIf(hsv, this.data.selectedColor);
- rgb = Ext.ux.colorpick.ColorUtils.hsv2rgb(hsv.h, hsv.s, hsv.v);
- hsv.r = rgb.r;
- hsv.g = rgb.g;
- hsv.b = rgb.b;
- this.set('selectedColor', hsv);
- },
- changeRGB: function(rgb) {
- var hsv;
- Ext.applyIf(rgb, this.data.selectedColor);
- hsv = Ext.ux.colorpick.ColorUtils.rgb2hsv(rgb.r, rgb.g, rgb.b);
- rgb.h = hsv.h;
- rgb.s = hsv.s;
- rgb.v = hsv.v;
- this.set('selectedColor', rgb);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.ux.colorpick.SelectorController', {
- extend: 'Ext.app.ViewController',
- alias: 'controller.colorpick-selectorcontroller',
- requires: [
- 'Ext.ux.colorpick.ColorUtils'
- ],
- destroy: function() {
- var me = this,
- view = me.getView(),
- childViewModel = view.childViewModel;
- if (childViewModel) {
- childViewModel.destroy();
- view.childViewModel = null;
- }
- me.callParent();
- },
- changeHSV: function(hsv) {
- var view = this.getView(),
- color = view.getColor(),
- rgb;
- // Put in values we are not changing (like A, but also missing HSV values)
- Ext.applyIf(hsv, color);
- // Now that HSV is complete, recalculate RGB and combine them
- rgb = Ext.ux.colorpick.ColorUtils.hsv2rgb(hsv.h, hsv.s, hsv.v);
- Ext.apply(hsv, rgb);
- view.setColor(hsv);
- },
- // Updates Saturation/Value in the model based on ColorMap; params:
- // xPercent - where is the handle relative to the color map width
- // yPercent - where is the handle relative to the color map height
- onColorMapHandleDrag: function(xPercent, yPercent) {
- this.changeHSV({
- s: xPercent,
- v: 1 - yPercent
- });
- },
- // Updates HSV Value in the model and downstream RGB settings
- onValueSliderHandleDrag: function(yPercent) {
- this.changeHSV({
- v: 1 - yPercent
- });
- },
- // Updates HSV Saturation in the model and downstream RGB settings
- onSaturationSliderHandleDrag: function(yPercent) {
- this.changeHSV({
- s: 1 - yPercent
- });
- },
- // Updates Hue in the model and downstream RGB settings
- onHueSliderHandleDrag: function(yPercent) {
- this.changeHSV({
- h: 1 - yPercent
- });
- },
- onAlphaSliderHandleDrag: function(yPercent) {
- var view = this.getView(),
- color = view.getColor(),
- newColor = Ext.applyIf({
- a: 1 - yPercent
- }, color);
- view.setColor(newColor);
- view.el.repaint();
- },
- onPreviousColorSelected: function(comp, color) {
- var view = this.getView();
- view.setColor(color);
- },
- onOK: function() {
- var me = this,
- view = me.getView();
- view.fireEvent('ok', view, view.getValue());
- },
- onCancel: function() {
- this.fireViewEvent('cancel', this.getView());
- },
- onResize: function() {
- var me = this,
- view = me.getView(),
- vm = view.childViewModel,
- refs = me.getReferences(),
- h, s, v, a;
- // Skip initial rendering resize
- if (!me.hasResizedOnce) {
- me.hasResizedOnce = true;
- return;
- }
- h = vm.get('hue');
- s = vm.get('saturation');
- v = vm.get('value');
- a = vm.get('alpha');
- // Reposition the colormap's & sliders' drag handles
- refs.colorMap.setPosition(vm.getData());
- refs.hueSlider.setHue(h);
- refs.satSlider.setSaturation(s);
- refs.valueSlider.setValue(v);
- refs.alphaSlider.setAlpha(a);
- }
- });
- /**
- * A basic component that changes background color, with considerations for opacity
- * support (checkered background image and IE8 support).
- */
- Ext.define('Ext.ux.colorpick.ColorPreview', {
- extend: 'Ext.Component',
- alias: 'widget.colorpickercolorpreview',
- requires: [
- 'Ext.util.Format',
- 'Ext.XTemplate'
- ],
- // hack to solve issue with IE, when applying a filter the click listener is not being fired.
- style: 'position: relative',
- /* eslint-disable max-len */
- 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>',
- /* eslint-enable max-len */
- // eo hack
- cls: Ext.baseCSSPrefix + 'colorpreview',
- height: 256,
- onRender: function() {
- var me = this;
- me.callParent(arguments);
- me.mon(me.el.down('.btn'), 'click', me.onClick, me);
- },
- onClick: function() {
- this.fireEvent('click', this, this.color);
- },
- // Called via databinding - update background color whenever ViewModel changes
- setColor: function(color) {
- var me = this,
- el = me.getEl();
- // Too early in rendering cycle; skip
- if (!el) {
- return;
- }
- me.color = color;
- me.applyBgStyle(color);
- },
- bgStyleTpl: Ext.create('Ext.XTemplate', Ext.isIE && Ext.ieVersion < 10 ? // eslint-disable-next-line max-len
- 'filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr=\'#{hexAlpha}{hex}\', endColorstr=\'#{hexAlpha}{hex}\');' : /* IE6-9 */
- 'background: {rgba};'),
- applyBgStyle: function(color) {
- var me = this,
- colorUtils = Ext.ux.colorpick.ColorUtils,
- filterSelector = '.' + Ext.baseCSSPrefix + 'colorpreview-filter',
- el = me.getEl().down(filterSelector),
- hex, alpha, rgba, bgStyle;
- hex = colorUtils.rgb2hex(color.r, color.g, color.b);
- alpha = Ext.util.Format.hex(Math.floor(color.a * 255), 2);
- rgba = colorUtils.getRGBAString(color);
- bgStyle = this.bgStyleTpl.apply({
- hex: hex,
- hexAlpha: alpha,
- rgba: rgba
- });
- el.applyStyles(bgStyle);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.ux.colorpick.SliderController', {
- extend: 'Ext.app.ViewController',
- alias: 'controller.colorpick-slidercontroller',
- // After the component is rendered
- boxReady: function(view) {
- var me = this,
- container = me.getDragContainer(),
- dragHandle = me.getDragHandle(),
- dd = dragHandle.dd;
- // configure draggable constraints
- dd.constrain = true;
- dd.constrainTo = container.getEl();
- dd.initialConstrainTo = dd.constrainTo;
- // needed otherwise error EXTJS-13187
- // event handlers
- dd.on('drag', me.onHandleDrag, me);
- },
- getDragHandle: function() {
- return this.view.lookupReference('dragHandle');
- },
- getDragContainer: function() {
- return this.view.lookupReference('dragHandleContainer');
- },
- // Fires when handle is dragged; fires "handledrag" event on the slider
- // with parameter "percentY" 0-1, representing the handle position on the slider
- // relative to the height
- onHandleDrag: function(e) {
- var me = this,
- view = me.getView(),
- container = me.getDragContainer(),
- dragHandle = me.getDragHandle(),
- y = dragHandle.getY() - container.getY(),
- containerEl = container.getEl(),
- containerHeight = containerEl.getHeight(),
- yRatio = y / containerHeight;
- // Adjust y ratio for dragger always being 1 pixel from the edge on the bottom
- if (yRatio > 0.99) {
- yRatio = 1;
- }
- view.fireEvent('handledrag', yRatio);
- },
- // Whenever we mousedown over the slider area
- onMouseDown: function(e) {
- var me = this,
- dragHandle = me.getDragHandle(),
- y = e.getY();
- // position drag handle accordingly
- dragHandle.setY(y);
- me.onHandleDrag();
- dragHandle.el.repaint();
- // tie into the default dd mechanism
- dragHandle.dd.onMouseDown(e, dragHandle.dd.el);
- },
- // Whenever we start a drag over the colormap area
- onDragStart: function(e) {
- var me = this,
- dragHandle = me.getDragHandle();
- // tie into the default dd mechanism
- dragHandle.dd.onDragStart(e, dragHandle.dd.el);
- },
- onMouseUp: function() {
- var dragHandle = this.getDragHandle();
- dragHandle.dd.dragEnded = true;
- }
- });
- // work around DragTracker bug
- /**
- * Parent view for the 4 sliders seen on the color picker window.
- * @private
- */
- Ext.define('Ext.ux.colorpick.Slider', {
- extend: 'Ext.container.Container',
- xtype: 'colorpickerslider',
- controller: 'colorpick-slidercontroller',
- afterRender: function() {
- var width, dragCt, dragWidth;
- this.callParent(arguments);
- width = this.width;
- dragCt = this.lookupReference('dragHandleContainer');
- dragWidth = dragCt.getWidth();
- dragCt.el.setStyle('left', ((width - dragWidth) / 2) + 'px');
- },
- baseCls: Ext.baseCSSPrefix + 'colorpicker-slider',
- requires: [
- 'Ext.ux.colorpick.SliderController'
- ],
- referenceHolder: true,
- listeners: {
- element: 'el',
- mousedown: 'onMouseDown',
- mouseup: 'onMouseUp',
- dragstart: 'onDragStart'
- },
- // Container for the drag handle; needed since the slider
- // is of static size, while the parent container positions
- // it in the center; this is what receives the beautiful
- // color gradients for the visual
- items: {
- xtype: 'container',
- cls: Ext.baseCSSPrefix + 'colorpicker-draghandle-container',
- reference: 'dragHandleContainer',
- height: '100%',
- // This is the drag handle; note it's 100%x1 in size to allow full
- // vertical drag travel; the inner div has the bigger image
- items: {
- xtype: 'component',
- cls: Ext.baseCSSPrefix + 'colorpicker-draghandle-outer',
- reference: 'dragHandle',
- width: '100%',
- height: 1,
- draggable: true,
- html: '<div class="' + Ext.baseCSSPrefix + 'colorpicker-draghandle"></div>'
- }
- },
- //<debug>
- // Called via data binding whenever selectedColor.h changes;
- setHue: function() {
- Ext.raise('Must implement setHue() in a child class!');
- },
- //</debug>
- getDragHandle: function() {
- return this.lookupReference('dragHandle');
- },
- getDragContainer: function() {
- return this.lookupReference('dragHandleContainer');
- }
- });
- /**
- * Used for "Alpha" slider.
- * @private
- */
- Ext.define('Ext.ux.colorpick.SliderAlpha', {
- extend: 'Ext.ux.colorpick.Slider',
- alias: 'widget.colorpickerslideralpha',
- cls: Ext.baseCSSPrefix + 'colorpicker-alpha',
- requires: [
- 'Ext.XTemplate'
- ],
- /* eslint-disable max-len */
- 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 */
- 'background: -moz-linear-gradient(top, rgba({r}, {g}, {b}, 1) 0%, rgba({r}, {g}, {b}, 0) 100%);' + /* FF3.6+ */
- 'background: -webkit-linear-gradient(top,rgba({r}, {g}, {b}, 1) 0%, rgba({r}, {g}, {b}, 0) 100%);' + /* Chrome10+,Safari5.1+ */
- 'background: -o-linear-gradient(top, rgba({r}, {g}, {b}, 1) 0%, rgba({r}, {g}, {b}, 0) 100%);' + /* Opera 11.10+ */
- 'background: -ms-linear-gradient(top, rgba({r}, {g}, {b}, 1) 0%, rgba({r}, {g}, {b}, 0) 100%);' + /* IE10+ */
- 'background: linear-gradient(to bottom, rgba({r}, {g}, {b}, 1) 0%, rgba({r}, {g}, {b}, 0) 100%);'),
- /* W3C */
- /* eslint-enable max-len */
- // Called via data binding whenever selectedColor.a changes; param is 0-100
- setAlpha: function(value) {
- var me = this,
- container = me.getDragContainer(),
- dragHandle = me.getDragHandle(),
- containerEl = container.getEl(),
- containerHeight = containerEl.getHeight(),
- el, top;
- // Too early in the render cycle? Skip event
- if (!dragHandle.dd || !dragHandle.dd.constrain) {
- return;
- }
- // User actively dragging? Skip event
- if (typeof dragHandle.dd.dragEnded !== 'undefined' && !dragHandle.dd.dragEnded) {
- return;
- }
- // y-axis of slider with value 0-1 translates to reverse of "value"
- top = containerHeight * (1 - (value / 100));
- // Position dragger
- el = dragHandle.getEl();
- el.setStyle({
- top: top + 'px'
- });
- },
- // Called via data binding whenever selectedColor.h changes; hue param is 0-1
- setColor: function(color) {
- var me = this,
- container = me.getDragContainer(),
- hex, el;
- // Too early in the render cycle? Skip event
- if (!me.getEl()) {
- return;
- }
- // Determine HEX for new hue and set as background based on template
- hex = Ext.ux.colorpick.ColorUtils.rgb2hex(color.r, color.g, color.b);
- el = container.getEl().first();
- el.applyStyles(me.gradientStyleTpl.apply({
- hex: hex,
- r: color.r,
- g: color.g,
- b: color.b
- }));
- }
- });
- /**
- * Used for "Saturation" slider
- * @private
- */
- Ext.define('Ext.ux.colorpick.SliderSaturation', {
- extend: 'Ext.ux.colorpick.Slider',
- alias: 'widget.colorpickerslidersaturation',
- cls: Ext.baseCSSPrefix + 'colorpicker-saturation',
- /* eslint-disable max-len */
- gradientStyleTpl: Ext.create('Ext.XTemplate', Ext.isIE && Ext.ieVersion < 10 ? 'filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr=\'#{hex}\', endColorstr=\'#ffffff\');' : /* IE6-9 */
- 'background: -mox-linear-gradient(top, #{hex} 0%, #ffffff 100%);' + /* FF3.6+ */
- 'background: -webkit-linear-gradient(top, #{hex} 0%,#ffffff 100%);' + /* Chrome10+,Safari5.1+ */
- 'background: -o-linear-gradient(top, #{hex} 0%,#ffffff 100%);' + /* Opera 11.10+ */
- 'background: -ms-linear-gradient(top, #{hex} 0%,#ffffff 100%);' + /* IE10+ */
- 'background: linear-gradient(to bottom, #{hex} 0%,#ffffff 100%);'),
- /* W3C */
- /* eslint-enable max-len */
- // Called via data binding whenever selectedColor.s changes; saturation param is 0-100
- setSaturation: function(saturation) {
- var me = this,
- container = me.getDragContainer(),
- dragHandle = me.getDragHandle(),
- containerEl = container.getEl(),
- containerHeight = containerEl.getHeight(),
- yRatio, top;
- // Too early in the render cycle? Skip event
- if (!dragHandle.dd || !dragHandle.dd.constrain) {
- return;
- }
- // User actively dragging? Skip event
- if (typeof dragHandle.dd.dragEnded !== 'undefined' && !dragHandle.dd.dragEnded) {
- return;
- }
- // y-axis of slider with value 0-1 translates to reverse of "saturation"
- yRatio = 1 - (saturation / 100);
- top = containerHeight * yRatio;
- // Position dragger
- dragHandle.getEl().setStyle({
- top: top + 'px'
- });
- },
- // Called via data binding whenever selectedColor.h changes; hue param is 0-1
- setHue: function(hue) {
- var me = this,
- container = me.getDragContainer(),
- rgb, hex;
- // Too early in the render cycle? Skip event
- if (!me.getEl()) {
- return;
- }
- // Determine HEX for new hue and set as background based on template
- rgb = Ext.ux.colorpick.ColorUtils.hsv2rgb(hue, 1, 1);
- hex = Ext.ux.colorpick.ColorUtils.rgb2hex(rgb.r, rgb.g, rgb.b);
- container.getEl().applyStyles(me.gradientStyleTpl.apply({
- hex: hex
- }));
- }
- });
- /**
- * Used for "Value" slider.
- * @private
- */
- Ext.define('Ext.ux.colorpick.SliderValue', {
- extend: 'Ext.ux.colorpick.Slider',
- alias: 'widget.colorpickerslidervalue',
- cls: Ext.baseCSSPrefix + 'colorpicker-value',
- requires: [
- 'Ext.XTemplate'
- ],
- /* eslint-disable max-len */
- gradientStyleTpl: Ext.create('Ext.XTemplate', Ext.isIE && Ext.ieVersion < 10 ? 'filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr=\'#{hex}\', endColorstr=\'#000000\');' : /* IE6-9 */
- 'background: -mox-linear-gradient(top, #{hex} 0%, #000000 100%);' + /* FF3.6+ */
- 'background: -webkit-linear-gradient(top, #{hex} 0%,#000000 100%);' + /* Chrome10+,Safari5.1+ */
- 'background: -o-linear-gradient(top, #{hex} 0%,#000000 100%);' + /* Opera 11.10+ */
- 'background: -ms-linear-gradient(top, #{hex} 0%,#000000 100%);' + /* IE10+ */
- 'background: linear-gradient(to bottom, #{hex} 0%,#000000 100%);'),
- /* W3C */
- /* eslint-enable max-len */
- // Called via data binding whenever selectedColor.v changes; value param is 0-100
- setValue: function(value) {
- var me = this,
- container = me.getDragContainer(),
- dragHandle = me.getDragHandle(),
- containerEl = container.getEl(),
- containerHeight = containerEl.getHeight(),
- yRatio, top;
- // Too early in the render cycle? Skip event
- if (!dragHandle.dd || !dragHandle.dd.constrain) {
- return;
- }
- // User actively dragging? Skip event
- if (typeof dragHandle.dd.dragEnded !== 'undefined' && !dragHandle.dd.dragEnded) {
- return;
- }
- // y-axis of slider with value 0-1 translates to reverse of "value"
- yRatio = 1 - (value / 100);
- top = containerHeight * yRatio;
- // Position dragger
- dragHandle.getEl().setStyle({
- top: top + 'px'
- });
- },
- // Called via data binding whenever selectedColor.h changes; hue param is 0-1
- setHue: function(hue) {
- var me = this,
- container = me.getDragContainer(),
- rgb, hex;
- // Too early in the render cycle? Skip event
- if (!me.getEl()) {
- return;
- }
- // Determine HEX for new hue and set as background based on template
- rgb = Ext.ux.colorpick.ColorUtils.hsv2rgb(hue, 1, 1);
- hex = Ext.ux.colorpick.ColorUtils.rgb2hex(rgb.r, rgb.g, rgb.b);
- container.getEl().applyStyles(me.gradientStyleTpl.apply({
- hex: hex
- }));
- }
- });
- /**
- * Used for "Hue" slider.
- * @private
- */
- Ext.define('Ext.ux.colorpick.SliderHue', {
- extend: 'Ext.ux.colorpick.Slider',
- alias: 'widget.colorpickersliderhue',
- cls: Ext.baseCSSPrefix + 'colorpicker-hue',
- afterRender: function() {
- var me = this,
- src = me.gradientUrl,
- el = me.el;
- me.callParent();
- if (!src) {
- // We do this trick to allow the Sass to calculate resource image path for
- // our package and pick up the proper image URL here.
- src = el.getStyle('background-image');
- src = src.substring(4, src.length - 1);
- // strip off outer "url(...)"
- // In IE8 this path will have quotes around it
- if (src.indexOf('"') === 0) {
- src = src.substring(1, src.length - 1);
- }
- // Then remember it on our prototype for any subsequent instances.
- Ext.ux.colorpick.SliderHue.prototype.gradientUrl = src;
- }
- // Now clear that style because it will conflict with the background-color
- el.setStyle('background-image', 'none');
- // Create the image with the background PNG
- el = me.getDragContainer().layout.getElementTarget();
- // the el for items and html
- el.createChild({
- tag: 'img',
- cls: Ext.baseCSSPrefix + 'colorpicker-hue-gradient',
- src: src
- });
- },
- // Called via data binding whenever selectedColor.h changes; hue param is 0-1
- setHue: function(hue) {
- var me = this,
- container = me.getDragContainer(),
- dragHandle = me.getDragHandle(),
- containerEl = container.getEl(),
- containerHeight = containerEl.getHeight(),
- el, top;
- // Too early in the render cycle? Skip event
- if (!dragHandle.dd || !dragHandle.dd.constrain) {
- return;
- }
- // User actively dragging? Skip event
- if (typeof dragHandle.dd.dragEnded !== 'undefined' && !dragHandle.dd.dragEnded) {
- return;
- }
- // y-axis of slider with value 0-1 translates to reverse of "hue"
- top = containerHeight * (1 - hue);
- // Position dragger
- el = dragHandle.getEl();
- el.setStyle({
- top: top + 'px'
- });
- }
- });
- /**
- * Sencha Pro Services presents xtype "colorselector".
- * API has been kept as close to the regular colorpicker as possible. The Selector can be
- * rendered to any container.
- *
- * The defaul selected color is configurable via {@link #value} config. Usually used in
- * forms via {@link Ext.ux.colorpick.Button} or {@link Ext.ux.colorpick.Field}.
- *
- * Typically you will need to listen for the change event to be notified when the user
- * chooses a color. Alternatively, you can bind to the "value" config
- *
- * @example
- * Ext.create('Ext.ux.colorpick.Selector', {
- * value: '993300', // initial selected color
- * renderTo: Ext.getBody(),
- * listeners: {
- * change: function (colorselector, color) {
- * console.log('New color: ' + color);
- * }
- * }
- * });
- */
- Ext.define('Ext.ux.colorpick.Selector', {
- extend: 'Ext.container.Container',
- xtype: 'colorselector',
- mixins: [
- 'Ext.ux.colorpick.Selection'
- ],
- controller: 'colorpick-selectorcontroller',
- requires: [
- 'Ext.layout.container.HBox',
- 'Ext.form.field.Text',
- 'Ext.form.field.Number',
- 'Ext.ux.colorpick.ColorMap',
- 'Ext.ux.colorpick.SelectorModel',
- 'Ext.ux.colorpick.SelectorController',
- 'Ext.ux.colorpick.ColorPreview',
- 'Ext.ux.colorpick.Slider',
- 'Ext.ux.colorpick.SliderAlpha',
- 'Ext.ux.colorpick.SliderSaturation',
- 'Ext.ux.colorpick.SliderValue',
- 'Ext.ux.colorpick.SliderHue'
- ],
- config: {
- hexReadOnly: true
- },
- width: 580,
- // default width and height gives 255x255 color map in Crisp
- height: 337,
- cls: Ext.baseCSSPrefix + 'colorpicker',
- padding: 10,
- layout: {
- type: 'hbox',
- align: 'stretch'
- },
- defaultBindProperty: 'value',
- twoWayBindable: [
- 'value'
- ],
- /**
- * @cfg fieldWidth {Number} Width of the text fields on the container (excluding HEX);
- * since the width of the slider containers is the same as the text field under it
- * (it's the same vbox column), changing this value will also affect the spacing between
- * the sliders.
- */
- fieldWidth: 50,
- /**
- * @cfg fieldPad {Number} padding between the sliders and HEX/R/G/B fields.
- */
- fieldPad: 5,
- /**
- * @cfg {Boolean} [showPreviousColor]
- * Whether "previous color" region (in upper right, below the selected color preview)
- * should be shown; these are relied upon by the {@link Ext.ux.colorpick.Button} and
- * the {@link Ext.ux.colorpick.Field}.
- */
- showPreviousColor: false,
- /**
- * @cfg {Boolean} [showOkCancelButtons]
- * Whether Ok and Cancel buttons (in upper right, below the selected color preview)
- * should be shown; these are relied upon by the {@link Ext.ux.colorpick.Button} and
- * the {@link Ext.ux.colorpick.Field}.
- */
- showOkCancelButtons: false,
- /**
- * @event change
- * Fires when a color is selected. Simply dragging sliders around will trigger this.
- * @param {Ext.ux.colorpick.Selector} this
- * @param {String} color The value of the selected color as per specified {@link #format}.
- * @param {String} previousColor The previous color value.
- */
- /**
- * @event ok
- * Fires when OK button is clicked (see {@link #showOkCancelButtons}).
- * @param {Ext.ux.colorpick.Selector} this
- * @param {String} color The value of the selected color as per specified {@link #format}.
- */
- /**
- * @event cancel
- * Fires when Cancel button is clicked (see {@link #showOkCancelButtons}).
- * @param {Ext.ux.colorpick.Selector} this
- */
- listeners: {
- resize: 'onResize'
- },
- constructor: function(config) {
- var me = this,
- childViewModel = Ext.Factory.viewModel('colorpick-selectormodel');
- // Since this component needs to present its value as a thing to which users can
- // bind, we create an internal VM for our purposes.
- me.childViewModel = childViewModel;
- me.items = [
- me.getMapAndHexRGBFields(childViewModel),
- me.getSliderAndHField(childViewModel),
- me.getSliderAndSField(childViewModel),
- me.getSliderAndVField(childViewModel),
- me.getSliderAndAField(childViewModel),
- me.getPreviewAndButtons(childViewModel, config)
- ];
- me.childViewModel.bind('{selectedColor}', function(color) {
- me.setColor(color);
- });
- me.callParent([
- config
- ]);
- },
- updateColor: function(color) {
- var me = this;
- me.mixins.colorselection.updateColor.call(me, color);
- me.childViewModel.set('selectedColor', color);
- },
- updatePreviousColor: function(color) {
- this.childViewModel.set('previousColor', color);
- },
- // Splits up view declaration for readability
- // "Map" and HEX/R/G/B fields
- getMapAndHexRGBFields: function(childViewModel) {
- var me = this,
- fieldMargin = {
- top: 0,
- right: me.fieldPad,
- bottom: 0,
- left: 0
- },
- fieldWidth = me.fieldWidth;
- return {
- xtype: 'container',
- viewModel: childViewModel,
- cls: Ext.baseCSSPrefix + 'colorpicker-escape-overflow',
- flex: 1,
- layout: {
- type: 'vbox',
- align: 'stretch'
- },
- margin: '0 10 0 0',
- items: [
- // "MAP"
- {
- xtype: 'colorpickercolormap',
- reference: 'colorMap',
- flex: 1,
- bind: {
- position: {
- bindTo: '{selectedColor}',
- deep: true
- },
- hue: '{selectedColor.h}'
- },
- listeners: {
- handledrag: 'onColorMapHandleDrag'
- }
- },
- // HEX/R/G/B FIELDS
- {
- xtype: 'container',
- layout: 'hbox',
- defaults: {
- labelAlign: 'top',
- labelSeparator: '',
- allowBlank: false,
- onChange: function() {
- // prevent data binding propagation if bad value
- if (this.isValid()) {
- // this is kind of dirty and ideally we would extend these fields
- // and override the method, but works for now
- Ext.form.field.Base.prototype.onChange.apply(this, arguments);
- }
- }
- },
- items: [
- {
- xtype: 'textfield',
- fieldLabel: 'HEX',
- flex: 1,
- bind: '{hex}',
- margin: fieldMargin,
- regex: /^#[0-9a-f]{6}$/i,
- readonly: me.getHexReadOnly()
- },
- {
- xtype: 'numberfield',
- fieldLabel: 'R',
- bind: '{red}',
- width: fieldWidth,
- hideTrigger: true,
- maxValue: 255,
- minValue: 0,
- margin: fieldMargin
- },
- {
- xtype: 'numberfield',
- fieldLabel: 'G',
- bind: '{green}',
- width: fieldWidth,
- hideTrigger: true,
- maxValue: 255,
- minValue: 0,
- margin: fieldMargin
- },
- {
- xtype: 'numberfield',
- fieldLabel: 'B',
- bind: '{blue}',
- width: fieldWidth,
- hideTrigger: true,
- maxValue: 255,
- minValue: 0,
- margin: 0
- }
- ]
- }
- ]
- };
- },
- // Splits up view declaration for readability
- // Slider and H field
- getSliderAndHField: function(childViewModel) {
- var me = this,
- fieldWidth = me.fieldWidth;
- return {
- xtype: 'container',
- viewModel: childViewModel,
- cls: Ext.baseCSSPrefix + 'colorpicker-escape-overflow',
- width: fieldWidth,
- layout: {
- type: 'vbox',
- align: 'stretch'
- },
- items: [
- {
- xtype: 'colorpickersliderhue',
- reference: 'hueSlider',
- flex: 1,
- bind: {
- hue: '{selectedColor.h}'
- },
- width: fieldWidth,
- listeners: {
- handledrag: 'onHueSliderHandleDrag'
- }
- },
- {
- xtype: 'numberfield',
- fieldLabel: 'H',
- labelAlign: 'top',
- labelSeparator: '',
- bind: '{hue}',
- hideTrigger: true,
- maxValue: 360,
- minValue: 0,
- allowBlank: false,
- margin: 0
- }
- ]
- };
- },
- // Splits up view declaration for readability
- // Slider and S field
- getSliderAndSField: function(childViewModel) {
- var me = this,
- fieldWidth = me.fieldWidth;
- return {
- xtype: 'container',
- viewModel: childViewModel,
- cls: Ext.baseCSSPrefix + 'colorpicker-escape-overflow',
- width: fieldWidth,
- layout: {
- type: 'vbox',
- align: 'stretch'
- },
- margin: {
- right: me.fieldPad,
- left: me.fieldPad
- },
- items: [
- {
- xtype: 'colorpickerslidersaturation',
- reference: 'satSlider',
- flex: 1,
- bind: {
- saturation: '{saturation}',
- hue: '{selectedColor.h}'
- },
- width: fieldWidth,
- listeners: {
- handledrag: 'onSaturationSliderHandleDrag'
- }
- },
- {
- xtype: 'numberfield',
- fieldLabel: 'S',
- labelAlign: 'top',
- labelSeparator: '',
- bind: '{saturation}',
- hideTrigger: true,
- maxValue: 100,
- minValue: 0,
- allowBlank: false,
- margin: 0
- }
- ]
- };
- },
- // Splits up view declaration for readability
- // Slider and V field
- getSliderAndVField: function(childViewModel) {
- var me = this,
- fieldWidth = me.fieldWidth;
- return {
- xtype: 'container',
- viewModel: childViewModel,
- cls: Ext.baseCSSPrefix + 'colorpicker-escape-overflow',
- width: fieldWidth,
- layout: {
- type: 'vbox',
- align: 'stretch'
- },
- items: [
- {
- xtype: 'colorpickerslidervalue',
- reference: 'valueSlider',
- flex: 1,
- bind: {
- value: '{value}',
- hue: '{selectedColor.h}'
- },
- width: fieldWidth,
- listeners: {
- handledrag: 'onValueSliderHandleDrag'
- }
- },
- {
- xtype: 'numberfield',
- fieldLabel: 'V',
- labelAlign: 'top',
- labelSeparator: '',
- bind: '{value}',
- hideTrigger: true,
- maxValue: 100,
- minValue: 0,
- allowBlank: false,
- margin: 0
- }
- ]
- };
- },
- // Splits up view declaration for readability
- // Slider and A field
- getSliderAndAField: function(childViewModel) {
- var me = this,
- fieldWidth = me.fieldWidth;
- return {
- xtype: 'container',
- viewModel: childViewModel,
- cls: Ext.baseCSSPrefix + 'colorpicker-escape-overflow',
- width: fieldWidth,
- layout: {
- type: 'vbox',
- align: 'stretch'
- },
- margin: {
- left: me.fieldPad
- },
- items: [
- {
- xtype: 'colorpickerslideralpha',
- reference: 'alphaSlider',
- flex: 1,
- bind: {
- alpha: '{alpha}',
- color: {
- bindTo: '{selectedColor}',
- deep: true
- }
- },
- width: fieldWidth,
- listeners: {
- handledrag: 'onAlphaSliderHandleDrag'
- }
- },
- {
- xtype: 'numberfield',
- fieldLabel: 'A',
- labelAlign: 'top',
- labelSeparator: '',
- bind: '{alpha}',
- hideTrigger: true,
- maxValue: 100,
- minValue: 0,
- allowBlank: false,
- margin: 0
- }
- ]
- };
- },
- // Splits up view declaration for readability
- // Preview current/previous color squares and OK and Cancel buttons
- getPreviewAndButtons: function(childViewModel, config) {
- // selected color preview is always shown
- var items = [
- {
- xtype: 'colorpickercolorpreview',
- flex: 1,
- bind: {
- color: {
- bindTo: '{selectedColor}',
- deep: true
- }
- }
- }
- ];
- // previous color preview is optional
- if (config.showPreviousColor) {
- items.push({
- xtype: 'colorpickercolorpreview',
- flex: 1,
- bind: {
- color: {
- bindTo: '{previousColor}',
- deep: true
- }
- },
- listeners: {
- click: 'onPreviousColorSelected'
- }
- });
- }
- // Ok/Cancel buttons are optional
- if (config.showOkCancelButtons) {
- items.push({
- xtype: 'button',
- text: 'OK',
- margin: '10 0 0 0',
- padding: '10 0 10 0',
- handler: 'onOK'
- }, {
- xtype: 'button',
- text: 'Cancel',
- margin: '10 0 0 0',
- padding: '10 0 10 0',
- handler: 'onCancel'
- });
- }
- return {
- xtype: 'container',
- viewModel: childViewModel,
- width: 70,
- margin: '0 0 0 10',
- items: items,
- layout: {
- type: 'vbox',
- align: 'stretch'
- }
- };
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.ux.colorpick.ButtonController', {
- extend: 'Ext.app.ViewController',
- alias: 'controller.colorpick-buttoncontroller',
- requires: [
- 'Ext.window.Window',
- 'Ext.layout.container.Fit',
- 'Ext.ux.colorpick.Selector',
- 'Ext.ux.colorpick.ColorUtils'
- ],
- afterRender: function(view) {
- view.updateColor(view.getColor());
- },
- destroy: function() {
- var view = this.getView(),
- colorPickerWindow = view.colorPickerWindow;
- if (colorPickerWindow) {
- colorPickerWindow.destroy();
- view.colorPickerWindow = view.colorPicker = null;
- }
- this.callParent();
- },
- getPopup: function() {
- var view = this.getView(),
- popup = view.colorPickerWindow,
- selector;
- if (!popup) {
- popup = Ext.create(view.getPopup());
- view.colorPickerWindow = popup;
- popup.colorPicker = view.colorPicker = selector = popup.lookupReference('selector');
- selector.setFormat(view.getFormat());
- selector.on({
- ok: 'onColorPickerOK',
- cancel: 'onColorPickerCancel',
- scope: this
- });
- popup.on({
- close: 'onColorPickerCancel',
- scope: this
- });
- }
- return popup;
- },
- // When button is clicked show the color picker window
- onClick: function() {
- var me = this,
- view = me.getView(),
- color = view.getColor(),
- popup = me.getPopup(),
- colorPicker = popup.colorPicker;
- colorPicker.setColor(color);
- colorPicker.setPreviousColor(color);
- popup.showBy(view, 'tl-br?');
- },
- onColorPickerOK: function(picker) {
- var view = this.getView(),
- color = picker.getColor(),
- cpWin = view.colorPickerWindow;
- cpWin.hide();
- view.setColor(color);
- },
- onColorPickerCancel: function() {
- var view = this.getView(),
- cpWin = view.colorPickerWindow;
- cpWin.hide();
- },
- syncColor: function(color) {
- var view = this.getView();
- Ext.ux.colorpick.ColorUtils.setBackground(view.filterEl, color);
- }
- });
- /**
- * A simple color swatch that can be clicked to bring up the color selector.
- *
- * The selected color is configurable via {@link #value}.
- *
- * @example
- * Ext.create('Ext.ux.colorpick.Button', {
- * value: '993300', // initial selected color
- * renderTo: Ext.getBody(),
- *
- * listeners: {
- * select: function(picker, selColor) {
- * Ext.Msg.alert('Color', selColor);
- * }
- * }
- * });
- */
- Ext.define('Ext.ux.colorpick.Button', {
- extend: 'Ext.Component',
- xtype: 'colorbutton',
- controller: 'colorpick-buttoncontroller',
- mixins: [
- 'Ext.ux.colorpick.Selection'
- ],
- requires: [
- 'Ext.ux.colorpick.ButtonController'
- ],
- baseCls: Ext.baseCSSPrefix + 'colorpicker-button',
- width: 20,
- height: 20,
- childEls: [
- 'btnEl',
- 'filterEl'
- ],
- config: {
- /**
- * @cfg {Object} popup
- * This object configures the popup window and colorselector component displayed
- * when this button is clicked. Applications should not need to configure this.
- * @private
- */
- popup: {
- lazy: true,
- $value: {
- xtype: 'window',
- closeAction: 'hide',
- referenceHolder: true,
- minWidth: 540,
- minHeight: 200,
- layout: 'fit',
- header: false,
- resizable: true,
- items: {
- xtype: 'colorselector',
- reference: 'selector',
- showPreviousColor: true,
- showOkCancelButtons: true
- }
- }
- }
- },
- defaultBindProperty: 'value',
- twoWayBindable: 'value',
- /* eslint-disable max-len */
- // Solve issue with IE, when applying a filter the click listener is not being fired.
- 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>',
- /* eslint-enable max-len */
- listeners: {
- click: 'onClick',
- element: 'btnEl'
- },
- /**
- * @event change
- * Fires when a color is selected.
- * @param {Ext.ux.colorpick.Selector} this
- * @param {String} color The value of the selected color as per specified {@link #format}.
- * @param {String} previousColor The previous color value.
- */
- updateColor: function(color) {
- var me = this,
- cp = me.colorPicker;
- me.mixins.colorselection.updateColor.call(me, color);
- Ext.ux.colorpick.ColorUtils.setBackground(me.filterEl, color);
- if (cp) {
- cp.setColor(color);
- }
- },
- // Sets this.format and color picker's setFormat()
- updateFormat: function(format) {
- var cp = this.colorPicker;
- if (cp) {
- cp.setFormat(format);
- }
- }
- });
- /**
- * A field that can be clicked to bring up the color picker.
- * The selected color is configurable via {@link #value}.
- *
- * @example
- * Ext.create({
- * xtype: 'colorfield',
- * renderTo: Ext.getBody(),
- *
- * value: '#993300', // initial selected color
- *
- * listeners : {
- * change: function(field, color) {
- * console.log('New color: ' + color);
- * }
- * }
- * });
- */
- Ext.define('Ext.ux.colorpick.Field', {
- extend: 'Ext.form.field.Picker',
- xtype: 'colorfield',
- mixins: [
- 'Ext.ux.colorpick.Selection'
- ],
- requires: [
- 'Ext.window.Window',
- 'Ext.ux.colorpick.Selector',
- 'Ext.ux.colorpick.ColorUtils',
- 'Ext.layout.container.Fit'
- ],
- editable: false,
- matchFieldWidth: false,
- // picker is usually wider than field
- // "Color Swatch" shown on the left of the field
- beforeBodyEl: [
- '<div class="' + Ext.baseCSSPrefix + 'colorpicker-field-swatch">' + '<div id="{id}-swatchEl" data-ref="swatchEl" class="' + Ext.baseCSSPrefix + 'colorpicker-field-swatch-inner"></div>' + '</div>'
- ],
- cls: Ext.baseCSSPrefix + 'colorpicker-field',
- childEls: [
- 'swatchEl'
- ],
- checkChangeEvents: [
- 'change'
- ],
- config: {
- /**
- * @cfg {Object} popup
- * This object configures the popup window and colorselector component displayed
- * when this button is clicked. Applications should not need to configure this.
- * @private
- */
- popup: {
- lazy: true,
- $value: {
- xtype: 'window',
- closeAction: 'hide',
- referenceHolder: true,
- minWidth: 540,
- minHeight: 200,
- layout: 'fit',
- header: false,
- resizable: true,
- items: {
- xtype: 'colorselector',
- reference: 'selector',
- showPreviousColor: true,
- showOkCancelButtons: true
- }
- }
- }
- },
- /**
- * @event change
- * Fires when a color is selected or if the field value is updated (if {@link #editable}).
- * @param {Ext.ux.colorpick.Field} this
- * @param {String} color The value of the selected color as per specified {@link #format}.
- * @param {String} previousColor The previous color value.
- */
- initComponent: function() {
- var me = this;
- me.callParent();
- me.on('change', me.onHexChange);
- },
- // NOTE: Since much of the logic of a picker class is overriding methods from the
- // base class, we don't bother to split out the small remainder as a controller.
- afterRender: function() {
- this.callParent();
- this.updateValue(this.value);
- },
- // override as required by parent pickerfield
- createPicker: function() {
- var me = this,
- popup = me.getPopup(),
- picker;
- // the window will actually be shown and will house the picker
- me.colorPickerWindow = popup = Ext.create(popup);
- me.colorPicker = picker = popup.lookupReference('selector');
- picker.setFormat(me.getFormat());
- picker.setColor(me.getColor());
- picker.setHexReadOnly(!me.editable);
- picker.on({
- ok: 'onColorPickerOK',
- cancel: 'onColorPickerCancel',
- scope: me
- });
- popup.on({
- close: 'onColorPickerCancel',
- scope: me
- });
- return me.colorPickerWindow;
- },
- // When the Ok button is clicked on color picker, preserve the previous value
- onColorPickerOK: function(colorPicker) {
- this.setColor(colorPicker.getColor());
- this.collapse();
- },
- onColorPickerCancel: function() {
- this.collapse();
- },
- onExpand: function() {
- var color = this.getColor();
- this.colorPicker.setPreviousColor(color);
- },
- onHexChange: function(field) {
- if (field.validate()) {
- this.setValue(field.getValue());
- }
- },
- // Expects value formatted as per "format" config
- setValue: function(color) {
- var me = this;
- if (Ext.ux.colorpick.ColorUtils.isValid(color)) {
- color = me.applyValue(color);
- me.callParent([
- color
- ]);
- // always update in case opacity changes, even if value doesn't have it
- // to handle "hex6" non-opacity type of format
- me.updateValue(color);
- }
- },
- // Sets this.format and color picker's setFormat()
- updateFormat: function(format) {
- var cp = this.colorPicker;
- if (cp) {
- cp.setFormat(format);
- }
- },
- updateValue: function(color) {
- var me = this,
- c;
- // If the "value" is changed, update "color" as well. Since these are always
- // tracking each other, we guard against the case where we are being updated
- // *because* "color" is being set.
- if (!me.syncing) {
- me.syncing = true;
- me.setColor(color);
- me.syncing = false;
- }
- c = me.getColor();
- if (c) {
- Ext.ux.colorpick.ColorUtils.setBackground(me.swatchEl, c);
- if (me.colorPicker) {
- me.colorPicker.setColor(c);
- }
- }
- },
- validator: function(val) {
- if (!Ext.ux.colorpick.ColorUtils.isValid(val)) {
- return this.invalidText;
- }
- return true;
- }
- });
- /**
- * Paging Memory Proxy, allows to use paging grid with in memory dataset
- */
- Ext.define('Ext.ux.data.PagingMemoryProxy', {
- extend: 'Ext.data.proxy.Memory',
- alias: 'proxy.pagingmemory',
- alternateClassName: 'Ext.data.PagingMemoryProxy',
- constructor: function() {
- Ext.log.warn('Ext.ux.data.PagingMemoryProxy functionality has been merged ' + 'into Ext.data.proxy.Memory by using the enablePaging flag.');
- this.callParent(arguments);
- },
- read: function(operation, callback, scope) {
- var reader = this.getReader(),
- result = reader.read(this.data),
- sorters, filters, sorterFn, records;
- scope = scope || this;
- // filtering
- filters = operation.filters;
- if (filters.length > 0) {
- // at this point we have an array of Ext.util.Filter objects to filter with,
- // so here we construct a function that combines these filters by ANDing them together
- records = [];
- Ext.each(result.records, function(record) {
- var isMatch = true,
- length = filters.length,
- filter, fn, scope, i;
- for (i = 0; i < length; i++) {
- filter = filters[i];
- fn = filter.filterFn;
- scope = filter.scope;
- isMatch = isMatch && fn.call(scope, record);
- }
- if (isMatch) {
- records.push(record);
- }
- }, this);
- result.records = records;
- result.totalRecords = result.total = records.length;
- }
- // sorting
- sorters = operation.sorters;
- if (sorters.length > 0) {
- // construct an amalgamated sorter function which combines all of the Sorters passed
- sorterFn = function(r1, r2) {
- var result = sorters[0].sort(r1, r2),
- length = sorters.length,
- i;
- // if we have more than one sorter, OR any additional sorter functions together
- for (i = 1; i < length; i++) {
- result = result || sorters[i].sort.call(this, r1, r2);
- }
- return result;
- };
- result.records.sort(sorterFn);
- }
- // paging (use undefined cause start can also be 0 (thus false))
- if (operation.start !== undefined && operation.limit !== undefined) {
- result.records = result.records.slice(operation.start, operation.start + operation.limit);
- result.count = result.records.length;
- }
- Ext.apply(operation, {
- resultSet: result
- });
- operation.setCompleted();
- operation.setSuccessful();
- Ext.defer(function() {
- Ext.callback(callback, scope, [
- operation
- ]);
- }, 10);
- }
- });
- /**
- * This class is used as a grid `plugin`. It provides a DropZone which cooperates with
- * DragZones whose dragData contains a "field" property representing a form Field.
- * Fields may be dropped onto grid data cells containing a matching data type.
- */
- Ext.define('Ext.ux.dd.CellFieldDropZone', {
- /* eslint-disable vars-on-top */
- extend: 'Ext.dd.DropZone',
- alias: 'plugin.ux-cellfielddropzone',
- containerScroll: true,
- /**
- * @cfg {Function/String} onCellDrop
- * The function to call on a cell data drop, or the name of the function on the
- * corresponding `{@link Ext.app.ViewController controller}`. For details on the
- * parameters, see `{@link #method!onCellDrop onCellDrop}`.
- */
- /**
- * This method is called when a field is dropped on a cell. This method is normally
- * replaced by the `{@link #cfg!onCellDrop onCellDrop}` config property passed to the
- * constructor.
- * @param {String} fieldName The name of the field.
- * @param {Mixed} value The value of the field.
- * @method onCellDrop
- */
- onCellDrop: Ext.emptyFn,
- constructor: function(cfg) {
- if (cfg) {
- var me = this,
- ddGroup = cfg.ddGroup,
- onCellDrop = cfg.onCellDrop;
- if (onCellDrop) {
- if (typeof onCellDrop === 'string') {
- me.onCellDropFn = onCellDrop;
- me.onCellDrop = me.callCellDrop;
- } else {
- me.onCellDrop = onCellDrop;
- }
- }
- if (ddGroup) {
- me.ddGroup = ddGroup;
- }
- }
- },
- init: function(grid) {
- var me = this;
- // Call the DropZone constructor using the View's scrolling element
- // only after the grid has been rendered.
- if (grid.rendered) {
- me.grid = grid;
- grid.getView().on({
- render: function(v) {
- me.view = v;
- Ext.ux.dd.CellFieldDropZone.superclass.constructor.call(me, me.view.el);
- },
- single: true
- });
- } else {
- grid.on('render', me.init, me, {
- single: true
- });
- }
- },
- getTargetFromEvent: function(e) {
- var me = this,
- v = me.view,
- // Ascertain whether the mousemove is within a grid cell
- cell = e.getTarget(v.getCellSelector());
- if (cell) {
- // We *are* within a grid cell, so ask the View exactly which one,
- // Extract data from the Model to create a target object for
- // processing in subsequent onNodeXXXX methods. Note that the target does
- // not have to be a DOM element. It can be whatever the noNodeXXX methods are
- // programmed to expect.
- var row = v.findItemByChild(cell),
- columnIndex = cell.cellIndex;
- if (row && Ext.isDefined(columnIndex)) {
- return {
- node: cell,
- record: v.getRecord(row),
- fieldName: me.grid.getVisibleColumnManager().getColumns()[columnIndex].dataIndex
- };
- }
- }
- },
- onNodeEnter: function(target, dd, e, dragData) {
- // On Node enter, see if it is valid for us to drop the field on that type of
- // column.
- delete this.dropOK;
- if (!target) {
- return;
- }
- // Check that a field is being dragged.
- var f = dragData.field;
- if (!f) {
- return;
- }
- // Check whether the data type of the column being dropped on accepts the
- // dragged field type. If so, set dropOK flag, and highlight the target node.
- var field = target.record.fieldsMap[target.fieldName];
- if (field.isNumeric) {
- if (!f.isXType('numberfield')) {
- return;
- }
- } else if (field.isDateField) {
- if (!f.isXType('datefield')) {
- return;
- }
- } else if (field.isBooleanField) {
- if (!f.isXType('checkbox')) {
- return;
- }
- }
- this.dropOK = true;
- Ext.fly(target.node).addCls('x-drop-target-active');
- },
- onNodeOver: function(target, dd, e, dragData) {
- // Return the class name to add to the drag proxy. This provides a visual
- // indication of drop allowed or not allowed.
- return this.dropOK ? this.dropAllowed : this.dropNotAllowed;
- },
- onNodeOut: function(target, dd, e, dragData) {
- Ext.fly(target.node).removeCls('x-drop-target-active');
- },
- onNodeDrop: function(target, dd, e, dragData) {
- // Process the drop event if we have previously ascertained that a drop is OK.
- if (this.dropOK) {
- var value = dragData.field.getValue();
- target.record.set(target.fieldName, value);
- this.onCellDrop(target.fieldName, value);
- return true;
- }
- },
- callCellDrop: function(fieldName, value) {
- Ext.callback(this.onCellDropFn, null, [
- fieldName,
- value
- ], 0, this.grid);
- }
- });
- Ext.define('Ext.ux.dd.PanelFieldDragZone', {
- extend: 'Ext.dd.DragZone',
- alias: 'plugin.ux-panelfielddragzone',
- scroll: false,
- constructor: function(cfg) {
- if (cfg) {
- if (cfg.ddGroup) {
- this.ddGroup = cfg.ddGroup;
- }
- }
- },
- init: function(panel) {
- var el;
- // Call the DragZone's constructor. The Panel must have been rendered.
- // Panel is an HtmlElement
- if (panel.nodeType) {
- // Called via dragzone::init
- Ext.ux.dd.PanelFieldDragZone.superclass.init.apply(this, arguments);
- } else // Panel is a Component - need the el
- {
- // Called via plugin::init
- if (panel.rendered) {
- el = panel.getEl();
- el.unselectable();
- Ext.ux.dd.PanelFieldDragZone.superclass.constructor.call(this, el);
- } else {
- panel.on('afterrender', this.init, this, {
- single: true
- });
- }
- }
- },
- getDragData: function(e) {
- // On mousedown, we ascertain whether it is on one of our draggable Fields.
- // If so, we collect data about the draggable object, and return a drag data
- // object which contains our own data, plus a "ddel" property which is a DOM
- // node which provides a "view" of the dragged data.
- var targetLabel = e.getTarget('label', null, true),
- text, oldMark, field, dragEl;
- if (targetLabel) {
- // Get the data we are dragging: the Field
- // create a ddel for the drag proxy to display
- field = Ext.getCmp(targetLabel.up('.' + Ext.form.Labelable.prototype.formItemCls).id);
- // Temporary prevent marking the field as invalid, since it causes changes
- // to the underlying dom element which can cause problems in IE
- oldMark = field.preventMark;
- field.preventMark = true;
- if (field.isValid()) {
- field.preventMark = oldMark;
- dragEl = document.createElement('div');
- dragEl.className = Ext.baseCSSPrefix + 'form-text';
- text = field.getRawValue();
- dragEl.innerHTML = Ext.isEmpty(text) ? ' ' : text;
- Ext.fly(dragEl).setWidth(field.getEl().getWidth());
- return {
- field: field,
- ddel: dragEl
- };
- }
- e.stopEvent();
- field.preventMark = oldMark;
- }
- },
- getRepairXY: function() {
- // The coordinates to slide the drag proxy back to on failed drop.
- return this.dragData.field.getEl().getXY();
- }
- });
- /**
- * Ext JS Library
- * Copyright(c) 2006-2014 Sencha Inc.
- * licensing@sencha.com
- * http://www.sencha.com/license
- *
- * @class Ext.ux.desktop.Desktop
- * @extends Ext.panel.Panel
- * <p>This class manages the wallpaper, shortcuts and taskbar.</p>
- */
- Ext.define('Ext.ux.desktop.Desktop', {
- extend: 'Ext.panel.Panel',
- alias: 'widget.desktop',
- uses: [
- 'Ext.util.MixedCollection',
- 'Ext.menu.Menu',
- 'Ext.view.View',
- // dataview
- 'Ext.window.Window',
- 'Ext.ux.desktop.TaskBar',
- 'Ext.ux.desktop.Wallpaper'
- ],
- activeWindowCls: 'ux-desktop-active-win',
- inactiveWindowCls: 'ux-desktop-inactive-win',
- lastActiveWindow: null,
- border: false,
- html: ' ',
- layout: 'fit',
- xTickSize: 1,
- yTickSize: 1,
- app: null,
- /**
- * @cfg {Array/Ext.data.Store} shortcuts
- * The items to add to the DataView. This can be a {@link Ext.data.Store Store} or a
- * simple array. Items should minimally provide the fields in the
- * {@link Ext.ux.desktop.ShortcutModel Shortcut}.
- */
- shortcuts: null,
- /**
- * @cfg {String} shortcutItemSelector
- * This property is passed to the DataView for the desktop to select shortcut items.
- * If the {@link #shortcutTpl} is modified, this will probably need to be modified as
- * well.
- */
- shortcutItemSelector: 'div.ux-desktop-shortcut',
- /**
- * @cfg {String} shortcutTpl
- * This XTemplate is used to render items in the DataView. If this is changed, the
- * {@link #shortcutItemSelector} will probably also need to changed.
- */
- /* eslint-disable indent */
- shortcutTpl: [
- '<tpl for=".">',
- '<div class="ux-desktop-shortcut" id="{name}-shortcut">',
- '<div class="ux-desktop-shortcut-icon {iconCls}">',
- '<img src="',
- Ext.BLANK_IMAGE_URL,
- '" title="{name}">',
- '</div>',
- '<span class="ux-desktop-shortcut-text">{name}</span>',
- '</div>',
- '</tpl>',
- '<div class="x-clear"></div>'
- ],
- /* eslint-enable indent */
- /**
- * @cfg {Object} taskbarConfig
- * The config object for the TaskBar.
- */
- taskbarConfig: null,
- windowMenu: null,
- initComponent: function() {
- var me = this,
- wallpaper;
- me.windowMenu = new Ext.menu.Menu(me.createWindowMenu());
- me.bbar = me.taskbar = new Ext.ux.desktop.TaskBar(me.taskbarConfig);
- me.taskbar.windowMenu = me.windowMenu;
- me.windows = new Ext.util.MixedCollection();
- me.contextMenu = new Ext.menu.Menu(me.createDesktopMenu());
- me.items = [
- {
- xtype: 'wallpaper',
- id: me.id + '_wallpaper'
- },
- me.createDataView()
- ];
- me.callParent();
- me.shortcutsView = me.items.getAt(1);
- me.shortcutsView.on('itemclick', me.onShortcutItemClick, me);
- wallpaper = me.wallpaper;
- me.wallpaper = me.items.getAt(0);
- if (wallpaper) {
- me.setWallpaper(wallpaper, me.wallpaperStretch);
- }
- },
- afterRender: function() {
- var me = this;
- me.callParent();
- me.el.on('contextmenu', me.onDesktopMenu, me);
- },
- //------------------------------------------------------
- // Overrideable configuration creation methods
- createDataView: function() {
- var me = this;
- return {
- xtype: 'dataview',
- overItemCls: 'x-view-over',
- trackOver: true,
- itemSelector: me.shortcutItemSelector,
- store: me.shortcuts,
- style: {
- position: 'absolute'
- },
- x: 0,
- y: 0,
- tpl: new Ext.XTemplate(me.shortcutTpl)
- };
- },
- createDesktopMenu: function() {
- var me = this,
- ret = {
- items: me.contextMenuItems || []
- };
- if (ret.items.length) {
- ret.items.push('-');
- }
- ret.items.push({
- text: 'Tile',
- handler: me.tileWindows,
- scope: me,
- minWindows: 1
- }, {
- text: 'Cascade',
- handler: me.cascadeWindows,
- scope: me,
- minWindows: 1
- });
- return ret;
- },
- createWindowMenu: function() {
- var me = this;
- return {
- defaultAlign: 'br-tr',
- items: [
- {
- text: 'Restore',
- handler: me.onWindowMenuRestore,
- scope: me
- },
- {
- text: 'Minimize',
- handler: me.onWindowMenuMinimize,
- scope: me
- },
- {
- text: 'Maximize',
- handler: me.onWindowMenuMaximize,
- scope: me
- },
- '-',
- {
- text: 'Close',
- handler: me.onWindowMenuClose,
- scope: me
- }
- ],
- listeners: {
- beforeshow: me.onWindowMenuBeforeShow,
- hide: me.onWindowMenuHide,
- scope: me
- }
- };
- },
- //------------------------------------------------------
- // Event handler methods
- onDesktopMenu: function(e) {
- var me = this,
- menu = me.contextMenu;
- e.stopEvent();
- if (!menu.rendered) {
- menu.on('beforeshow', me.onDesktopMenuBeforeShow, me);
- }
- menu.showAt(e.getXY());
- menu.doConstrain();
- },
- onDesktopMenuBeforeShow: function(menu) {
- var me = this,
- count = me.windows.getCount();
- menu.items.each(function(item) {
- var min = item.minWindows || 0;
- item.setDisabled(count < min);
- });
- },
- onShortcutItemClick: function(dataView, record) {
- var me = this,
- module = me.app.getModule(record.data.module),
- win = module && module.createWindow();
- if (win) {
- me.restoreWindow(win);
- }
- },
- onWindowClose: function(win) {
- var me = this;
- me.windows.remove(win);
- me.taskbar.removeTaskButton(win.taskButton);
- me.updateActiveWindow();
- },
- //------------------------------------------------------
- // Window context menu handlers
- onWindowMenuBeforeShow: function(menu) {
- var items = menu.items.items,
- win = menu.theWin;
- items[0].setDisabled(win.maximized !== true && win.hidden !== true);
- // Restore
- items[1].setDisabled(win.minimized === true);
- // Minimize
- items[2].setDisabled(win.maximized === true || win.hidden === true);
- },
- // Maximize
- onWindowMenuClose: function() {
- var me = this,
- win = me.windowMenu.theWin;
- win.close();
- },
- onWindowMenuHide: function(menu) {
- Ext.defer(function() {
- menu.theWin = null;
- }, 1);
- },
- onWindowMenuMaximize: function() {
- var me = this,
- win = me.windowMenu.theWin;
- win.maximize();
- win.toFront();
- },
- onWindowMenuMinimize: function() {
- var me = this,
- win = me.windowMenu.theWin;
- win.minimize();
- },
- onWindowMenuRestore: function() {
- var me = this,
- win = me.windowMenu.theWin;
- me.restoreWindow(win);
- },
- //------------------------------------------------------
- // Dynamic (re)configuration methods
- getWallpaper: function() {
- return this.wallpaper.wallpaper;
- },
- setTickSize: function(xTickSize, yTickSize) {
- var me = this,
- xt = me.xTickSize = xTickSize,
- yt = me.yTickSize = (arguments.length > 1) ? yTickSize : xt;
- me.windows.each(function(win) {
- var dd = win.dd,
- resizer = win.resizer;
- dd.xTickSize = xt;
- dd.yTickSize = yt;
- resizer.widthIncrement = xt;
- resizer.heightIncrement = yt;
- });
- },
- setWallpaper: function(wallpaper, stretch) {
- this.wallpaper.setWallpaper(wallpaper, stretch);
- return this;
- },
- //------------------------------------------------------
- // Window management methods
- cascadeWindows: function() {
- var x = 0,
- y = 0,
- zmgr = this.getDesktopZIndexManager();
- zmgr.eachBottomUp(function(win) {
- if (win.isWindow && win.isVisible() && !win.maximized) {
- win.setPosition(x, y);
- x += 20;
- y += 20;
- }
- });
- },
- createWindow: function(config, cls) {
- var me = this,
- win,
- cfg = Ext.applyIf(config || {}, {
- stateful: false,
- isWindow: true,
- constrainHeader: true,
- minimizable: true,
- maximizable: true
- });
- cls = cls || Ext.window.Window;
- win = me.add(new cls(cfg));
- me.windows.add(win);
- win.taskButton = me.taskbar.addTaskButton(win);
- win.animateTarget = win.taskButton.el;
- win.on({
- activate: me.updateActiveWindow,
- beforeshow: me.updateActiveWindow,
- deactivate: me.updateActiveWindow,
- minimize: me.minimizeWindow,
- destroy: me.onWindowClose,
- scope: me
- });
- win.on({
- boxready: function() {
- win.dd.xTickSize = me.xTickSize;
- win.dd.yTickSize = me.yTickSize;
- if (win.resizer) {
- win.resizer.widthIncrement = me.xTickSize;
- win.resizer.heightIncrement = me.yTickSize;
- }
- },
- single: true
- });
- // replace normal window close w/fadeOut animation:
- win.doClose = function() {
- win.doClose = Ext.emptyFn;
- // dblclick can call again...
- win.el.disableShadow();
- win.el.fadeOut({
- listeners: {
- afteranimate: function() {
- win.destroy();
- }
- }
- });
- };
- return win;
- },
- getActiveWindow: function() {
- var win = null,
- zmgr = this.getDesktopZIndexManager();
- if (zmgr) {
- // We cannot rely on activate/deactive because that fires against non-Window
- // components in the stack.
- zmgr.eachTopDown(function(comp) {
- if (comp.isWindow && !comp.hidden) {
- win = comp;
- return false;
- }
- return true;
- });
- }
- return win;
- },
- getDesktopZIndexManager: function() {
- var windows = this.windows;
- // TODO - there has to be a better way to get this...
- return (windows.getCount() && windows.getAt(0).zIndexManager) || null;
- },
- getWindow: function(id) {
- return this.windows.get(id);
- },
- minimizeWindow: function(win) {
- win.minimized = true;
- win.hide();
- },
- restoreWindow: function(win) {
- if (win.isVisible()) {
- win.restore();
- win.toFront();
- } else {
- win.show();
- }
- return win;
- },
- tileWindows: function() {
- var me = this,
- availWidth = me.body.getWidth(true),
- x = me.xTickSize,
- y = me.yTickSize,
- nextY = y;
- me.windows.each(function(win) {
- var w;
- if (win.isVisible() && !win.maximized) {
- w = win.el.getWidth();
- // Wrap to next row if we are not at the line start and this Window will
- // go off the end
- if (x > me.xTickSize && x + w > availWidth) {
- x = me.xTickSize;
- y = nextY;
- }
- win.setPosition(x, y);
- x += w + me.xTickSize;
- nextY = Math.max(nextY, y + win.el.getHeight() + me.yTickSize);
- }
- });
- },
- updateActiveWindow: function() {
- var me = this,
- activeWindow = me.getActiveWindow(),
- last = me.lastActiveWindow;
- if (last && last.destroyed) {
- me.lastActiveWindow = null;
- return;
- }
- if (activeWindow === last) {
- return;
- }
- if (last) {
- if (last.el.dom) {
- last.addCls(me.inactiveWindowCls);
- last.removeCls(me.activeWindowCls);
- }
- last.active = false;
- }
- me.lastActiveWindow = activeWindow;
- if (activeWindow) {
- activeWindow.addCls(me.activeWindowCls);
- activeWindow.removeCls(me.inactiveWindowCls);
- activeWindow.minimized = false;
- activeWindow.active = true;
- }
- me.taskbar.setActiveButton(activeWindow && activeWindow.taskButton);
- }
- });
- /**
- * Ext JS Library
- * Copyright(c) 2006-2014 Sencha Inc.
- * licensing@sencha.com
- * http://www.sencha.com/license
- * @class Ext.ux.desktop.App
- */
- Ext.define('Ext.ux.desktop.App', {
- mixins: {
- observable: 'Ext.util.Observable'
- },
- requires: [
- 'Ext.container.Viewport',
- 'Ext.ux.desktop.Desktop'
- ],
- isReady: false,
- modules: null,
- useQuickTips: true,
- constructor: function(config) {
- var me = this;
- me.mixins.observable.constructor.call(this, config);
- if (Ext.isReady) {
- Ext.defer(me.init, 10, me);
- } else {
- Ext.onReady(me.init, me);
- }
- },
- init: function() {
- var me = this,
- desktopCfg;
- if (me.useQuickTips) {
- Ext.QuickTips.init();
- }
- me.modules = me.getModules();
- if (me.modules) {
- me.initModules(me.modules);
- }
- desktopCfg = me.getDesktopConfig();
- me.desktop = new Ext.ux.desktop.Desktop(desktopCfg);
- me.viewport = new Ext.container.Viewport({
- layout: 'fit',
- items: [
- me.desktop
- ]
- });
- Ext.getWin().on('beforeunload', me.onUnload, me);
- me.isReady = true;
- me.fireEvent('ready', me);
- },
- /**
- * This method returns the configuration object for the Desktop object. A derived
- * class can override this method, call the base version to build the config and
- * then modify the returned object before returning it.
- */
- getDesktopConfig: function() {
- var me = this,
- cfg = {
- app: me,
- taskbarConfig: me.getTaskbarConfig()
- };
- Ext.apply(cfg, me.desktopConfig);
- return cfg;
- },
- getModules: Ext.emptyFn,
- /**
- * This method returns the configuration object for the Start Button. A derived
- * class can override this method, call the base version to build the config and
- * then modify the returned object before returning it.
- */
- getStartConfig: function() {
- var me = this,
- cfg = {
- app: me,
- menu: []
- },
- launcher;
- Ext.apply(cfg, me.startConfig);
- Ext.each(me.modules, function(module) {
- launcher = module.launcher;
- if (launcher) {
- launcher.handler = launcher.handler || Ext.bind(me.createWindow, me, [
- module
- ]);
- cfg.menu.push(module.launcher);
- }
- });
- return cfg;
- },
- createWindow: function(module) {
- var window = module.createWindow();
- window.show();
- },
- /**
- * This method returns the configuration object for the TaskBar. A derived class
- * can override this method, call the base version to build the config and then
- * modify the returned object before returning it.
- */
- getTaskbarConfig: function() {
- var me = this,
- cfg = {
- app: me,
- startConfig: me.getStartConfig()
- };
- Ext.apply(cfg, me.taskbarConfig);
- return cfg;
- },
- initModules: function(modules) {
- var me = this;
- Ext.each(modules, function(module) {
- module.app = me;
- });
- },
- getModule: function(name) {
- var ms = this.modules,
- i, len, m;
- for (i = 0 , len = ms.length; i < len; i++) {
- m = ms[i];
- // eslint-disable-next-line eqeqeq
- if (m.id == name || m.appType == name) {
- return m;
- }
- }
- return null;
- },
- onReady: function(fn, scope) {
- if (this.isReady) {
- fn.call(scope, this);
- } else {
- this.on({
- ready: fn,
- scope: scope,
- single: true
- });
- }
- },
- getDesktop: function() {
- return this.desktop;
- },
- onUnload: function(e) {
- if (this.fireEvent('beforeunload', this) === false) {
- e.stopEvent();
- }
- }
- });
- /*
- * Ext JS Library
- * Copyright(c) 2006-2014 Sencha Inc.
- * licensing@sencha.com
- * http://www.sencha.com/license
- */
- Ext.define('Ext.ux.desktop.Module', {
- mixins: {
- observable: 'Ext.util.Observable'
- },
- constructor: function(config) {
- this.mixins.observable.constructor.call(this, config);
- this.init();
- },
- init: Ext.emptyFn
- });
- /*
- * Ext JS Library
- * Copyright(c) 2006-2014 Sencha Inc.
- * licensing@sencha.com
- * http://www.sencha.com/license
- */
- /**
- * @class Ext.ux.desktop.ShortcutModel
- * @extends Ext.data.Model
- * This model defines the minimal set of fields for desktop shortcuts.
- */
- Ext.define('Ext.ux.desktop.ShortcutModel', {
- extend: 'Ext.data.Model',
- fields: [
- {
- name: 'name',
- convert: Ext.String.createVarName
- },
- {
- name: 'iconCls'
- },
- {
- name: 'module'
- }
- ]
- });
- /**
- * Ext JS Library
- * Copyright(c) 2006-2014 Sencha Inc.
- * licensing@sencha.com
- * http://www.sencha.com/license
- * @class Ext.ux.desktop.StartMenu
- */
- Ext.define('Ext.ux.desktop.StartMenu', {
- extend: 'Ext.menu.Menu',
- // We want header styling like a Panel
- baseCls: Ext.baseCSSPrefix + 'panel',
- // Special styling within
- cls: 'x-menu ux-start-menu',
- bodyCls: 'ux-start-menu-body',
- defaultAlign: 'bl-tl',
- iconCls: 'user',
- bodyBorder: true,
- width: 300,
- initComponent: function() {
- var me = this;
- me.layout.align = 'stretch';
- me.items = me.menu;
- me.callParent();
- me.toolbar = new Ext.toolbar.Toolbar(Ext.apply({
- dock: 'right',
- cls: 'ux-start-menu-toolbar',
- vertical: true,
- width: 100,
- layout: {
- align: 'stretch'
- }
- }, me.toolConfig));
- me.addDocked(me.toolbar);
- delete me.toolItems;
- },
- addMenuItem: function() {
- var cmp = this.menu;
- cmp.add.apply(cmp, arguments);
- },
- addToolItem: function() {
- var cmp = this.toolbar;
- cmp.add.apply(cmp, arguments);
- }
- });
- // StartMenu
- /*
- * Ext JS Library
- * Copyright(c) 2006-2014 Sencha Inc.
- * licensing@sencha.com
- * http://www.sencha.com/license
- */
- /**
- * @class Ext.ux.desktop.TaskBar
- * @extends Ext.toolbar.Toolbar
- */
- Ext.define('Ext.ux.desktop.TaskBar', {
- // This must be a toolbar. we rely on acquired toolbar classes and inherited toolbar methods
- // for our child items to instantiate and render correctly.
- extend: 'Ext.toolbar.Toolbar',
- requires: [
- 'Ext.button.Button',
- 'Ext.resizer.Splitter',
- 'Ext.menu.Menu',
- 'Ext.ux.desktop.StartMenu'
- ],
- alias: 'widget.taskbar',
- cls: 'ux-taskbar',
- /**
- * @cfg {String} startBtnText
- * The text for the Start Button.
- */
- startBtnText: 'Start',
- initComponent: function() {
- var me = this;
- me.startMenu = new Ext.ux.desktop.StartMenu(me.startConfig);
- me.quickStart = new Ext.toolbar.Toolbar(me.getQuickStart());
- me.windowBar = new Ext.toolbar.Toolbar(me.getWindowBarConfig());
- me.tray = new Ext.toolbar.Toolbar(me.getTrayConfig());
- me.items = [
- {
- xtype: 'button',
- cls: 'ux-start-button',
- iconCls: 'ux-start-button-icon',
- menu: me.startMenu,
- menuAlign: 'bl-tl',
- text: me.startBtnText
- },
- me.quickStart,
- {
- xtype: 'splitter',
- html: ' ',
- height: 14,
- width: 2,
- // TODO - there should be a CSS way here
- cls: 'x-toolbar-separator x-toolbar-separator-horizontal'
- },
- me.windowBar,
- '-',
- me.tray
- ];
- me.callParent();
- },
- afterLayout: function() {
- var me = this;
- me.callParent();
- me.windowBar.el.on('contextmenu', me.onButtonContextMenu, me);
- },
- /**
- * This method returns the configuration object for the Quick Start toolbar. A derived
- * class can override this method, call the base version to build the config and
- * then modify the returned object before returning it.
- */
- getQuickStart: function() {
- var me = this,
- ret = {
- minWidth: 20,
- width: Ext.themeName === 'neptune' ? 70 : 60,
- items: [],
- enableOverflow: true
- };
- Ext.each(this.quickStart, function(item) {
- ret.items.push({
- tooltip: {
- text: item.name,
- align: 'bl-tl'
- },
- // tooltip: item.name,
- overflowText: item.name,
- iconCls: item.iconCls,
- module: item.module,
- handler: me.onQuickStartClick,
- scope: me
- });
- });
- return ret;
- },
- /**
- * This method returns the configuration object for the Tray toolbar. A derived
- * class can override this method, call the base version to build the config and
- * then modify the returned object before returning it.
- */
- getTrayConfig: function() {
- var ret = {
- items: this.trayItems
- };
- delete this.trayItems;
- return ret;
- },
- getWindowBarConfig: function() {
- return {
- flex: 1,
- cls: 'ux-desktop-windowbar',
- items: [
- ' '
- ],
- layout: {
- overflowHandler: 'Scroller'
- }
- };
- },
- getWindowBtnFromEl: function(el) {
- var c = this.windowBar.getChildByElement(el);
- return c || null;
- },
- onQuickStartClick: function(btn) {
- var module = this.app.getModule(btn.module),
- window;
- if (module) {
- window = module.createWindow();
- window.show();
- }
- },
- onButtonContextMenu: function(e) {
- var me = this,
- t = e.getTarget(),
- btn = me.getWindowBtnFromEl(t);
- if (btn) {
- e.stopEvent();
- me.windowMenu.theWin = btn.win;
- me.windowMenu.showBy(t);
- }
- },
- onWindowBtnClick: function(btn) {
- var win = btn.win;
- if (win.minimized || win.hidden) {
- btn.disable();
- win.show(null, function() {
- btn.enable();
- });
- } else if (win.active) {
- btn.disable();
- win.on('hide', function() {
- btn.enable();
- }, null, {
- single: true
- });
- win.minimize();
- } else {
- win.toFront();
- }
- },
- addTaskButton: function(win) {
- var config = {
- iconCls: win.iconCls,
- enableToggle: true,
- toggleGroup: 'all',
- width: 140,
- margin: '0 2 0 3',
- text: Ext.util.Format.ellipsis(win.title, 20),
- listeners: {
- click: this.onWindowBtnClick,
- scope: this
- },
- win: win
- },
- cmp = this.windowBar.add(config);
- cmp.toggle(true);
- return cmp;
- },
- removeTaskButton: function(btn) {
- var found,
- me = this;
- me.windowBar.items.each(function(item) {
- if (item === btn) {
- found = item;
- }
- return !found;
- });
- if (found) {
- me.windowBar.remove(found);
- }
- return found;
- },
- setActiveButton: function(btn) {
- if (btn) {
- btn.toggle(true);
- } else {
- this.windowBar.items.each(function(item) {
- if (item.isButton) {
- item.toggle(false);
- }
- });
- }
- }
- });
- /**
- * @class Ext.ux.desktop.TrayClock
- * @extends Ext.toolbar.TextItem
- * This class displays a clock on the toolbar.
- */
- Ext.define('Ext.ux.desktop.TrayClock', {
- extend: 'Ext.toolbar.TextItem',
- alias: 'widget.trayclock',
- cls: 'ux-desktop-trayclock',
- html: ' ',
- timeFormat: 'g:i A',
- tpl: '{time}',
- initComponent: function() {
- var me = this;
- me.callParent();
- if (typeof (me.tpl) === 'string') {
- me.tpl = new Ext.XTemplate(me.tpl);
- }
- },
- afterRender: function() {
- var me = this;
- Ext.defer(me.updateTime, 100, me);
- me.callParent();
- },
- doDestroy: function() {
- var me = this;
- if (me.timer) {
- window.clearTimeout(me.timer);
- me.timer = null;
- }
- me.callParent();
- },
- updateTime: function() {
- var me = this,
- time = Ext.Date.format(new Date(), me.timeFormat),
- text = me.tpl.apply({
- time: time
- });
- if (me.lastText !== text) {
- me.setText(text);
- me.lastText = text;
- }
- me.timer = Ext.defer(me.updateTime, 10000, me);
- }
- });
- /*
- * Ext JS Library
- * Copyright(c) 2006-2015 Sencha Inc.
- * licensing@sencha.com
- * http://www.sencha.com/license
- */
- /**
- * From code originally written by David Davis
- *
- * For HTML5 video to work, your server must
- * send the right content type, for more info see:
- * <http://developer.mozilla.org/En/HTML/Element/Video>
- * @class Ext.ux.desktop.Video
- */
- Ext.define('Ext.ux.desktop.Video', {
- extend: 'Ext.panel.Panel',
- alias: 'widget.video',
- layout: 'fit',
- autoplay: false,
- controls: true,
- bodyStyle: 'background-color:#000;color:#fff',
- html: '',
- /* eslint-disable max-len, indent */
- tpl: [
- '<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%">',
- '<tpl for="src">',
- '<source src="{src}" type="{type}"/>',
- '</tpl>',
- '{html}',
- '</video>'
- ],
- /* eslint-enable max-len, indent */
- initComponent: function() {
- var me = this,
- fallback, cfg, chrome;
- if (me.fallbackHTML) {
- fallback = me.fallbackHTML;
- } else {
- fallback = "Your browser does not support HTML5 Video. ";
- if (Ext.isChrome) {
- fallback += 'Upgrade Chrome.';
- } else if (Ext.isGecko) {
- fallback += 'Upgrade to Firefox 3.5 or newer.';
- } else {
- chrome = '<a href="http://www.google.com/chrome">Chrome</a>';
- fallback += 'Please try <a href="http://www.mozilla.com">Firefox</a>';
- if (Ext.isIE) {
- fallback += ', ' + chrome + ' or <a href="http://www.apple.com/safari/">Safari</a>.';
- } else {
- fallback += ' or ' + chrome + '.';
- }
- }
- }
- me.fallbackHTML = fallback;
- cfg = me.data = Ext.copyTo({
- tag: 'video',
- html: fallback
- }, me, 'id,poster,start,loopstart,loopend,playcount,autobuffer,loop');
- // just having the params exist enables them
- if (me.autoplay) {
- cfg.autoplay = 1;
- }
- if (me.controls) {
- cfg.controls = 1;
- }
- // handle multiple sources
- if (Ext.isArray(me.src)) {
- cfg.src = me.src;
- } else {
- cfg.src = [
- {
- src: me.src
- }
- ];
- }
- me.callParent();
- },
- afterRender: function() {
- var me = this,
- el;
- me.callParent();
- me.video = me.body.getById(me.id + '-video');
- el = me.video.dom;
- me.supported = (el && el.tagName.toLowerCase() === 'video');
- if (me.supported) {
- me.video.on('error', me.onVideoError, me);
- }
- },
- getFallback: function() {
- return '<h1 style="background-color:#ff4f4f;padding: 10px;">' + this.fallbackHTML + '</h1>';
- },
- onVideoError: function() {
- var me = this;
- me.video.remove();
- me.supported = false;
- me.body.createChild(me.getFallback());
- },
- doDestroy: function() {
- var me = this,
- video = me.video,
- videoDom;
- video = me.video;
- if (me.supported && video) {
- videoDom = video.dom;
- if (videoDom && videoDom.pause) {
- videoDom.pause();
- }
- video.remove();
- me.video = null;
- }
- me.callParent();
- }
- });
- /*
- * Ext JS Library
- * Copyright(c) 2006-2014 Sencha Inc.
- * licensing@sencha.com
- * http://www.sencha.com/license
- */
- /**
- * @class Ext.ux.desktop.Wallpaper
- * @extends Ext.Component
- * <p>This component renders an image that stretches to fill the component.</p>
- */
- Ext.define('Ext.ux.desktop.Wallpaper', {
- extend: 'Ext.Component',
- alias: 'widget.wallpaper',
- cls: 'ux-wallpaper',
- html: '<img src="' + Ext.BLANK_IMAGE_URL + '">',
- stretch: false,
- wallpaper: null,
- stateful: true,
- stateId: 'desk-wallpaper',
- afterRender: function() {
- var me = this;
- me.callParent();
- me.setWallpaper(me.wallpaper, me.stretch);
- },
- applyState: function() {
- var me = this,
- old = me.wallpaper;
- me.callParent(arguments);
- if (old !== me.wallpaper) {
- me.setWallpaper(me.wallpaper);
- }
- },
- getState: function() {
- return this.wallpaper && {
- wallpaper: this.wallpaper
- };
- },
- setWallpaper: function(wallpaper, stretch) {
- var me = this,
- imgEl, bkgnd;
- me.stretch = (stretch !== false);
- me.wallpaper = wallpaper;
- if (me.rendered) {
- imgEl = me.el.dom.firstChild;
- if (!wallpaper || wallpaper === Ext.BLANK_IMAGE_URL) {
- Ext.fly(imgEl).hide();
- } else if (me.stretch) {
- imgEl.src = wallpaper;
- me.el.removeCls('ux-wallpaper-tiled');
- Ext.fly(imgEl).setStyle({
- width: '100%',
- height: '100%'
- }).show();
- } else {
- Ext.fly(imgEl).hide();
- bkgnd = 'url(' + wallpaper + ')';
- me.el.addCls('ux-wallpaper-tiled');
- }
- me.el.setStyle({
- backgroundImage: bkgnd || ''
- });
- if (me.stateful) {
- me.saveState();
- }
- }
- return me;
- }
- });
- /**
- * Recorder manager.
- * Used as a bookmarklet:
- *
- * javascript:void(window.open("../ux/event/RecorderManager.html","recmgr"))
- */
- Ext.define('Ext.ux.event.RecorderManager', {
- extend: 'Ext.panel.Panel',
- alias: 'widget.eventrecordermanager',
- uses: [
- 'Ext.ux.event.Recorder',
- 'Ext.ux.event.Player'
- ],
- layout: 'fit',
- buttonAlign: 'left',
- eventsToIgnore: {
- mousemove: 1,
- mouseover: 1,
- mouseout: 1
- },
- bodyBorder: false,
- playSpeed: 1,
- initComponent: function() {
- var me = this,
- events;
- me.recorder = new Ext.ux.event.Recorder({
- attachTo: me.attachTo,
- listeners: {
- add: me.updateEvents,
- coalesce: me.updateEvents,
- buffer: 200,
- scope: me
- }
- });
- me.recorder.eventsToRecord = Ext.apply({}, me.recorder.eventsToRecord);
- function speed(text, value) {
- return {
- text: text,
- speed: value,
- group: 'speed',
- checked: value === me.playSpeed,
- handler: me.onPlaySpeed,
- scope: me
- };
- }
- me.tbar = [
- {
- text: 'Record',
- xtype: 'splitbutton',
- whenIdle: true,
- handler: me.onRecord,
- scope: me,
- menu: me.makeRecordButtonMenu()
- },
- {
- text: 'Play',
- xtype: 'splitbutton',
- whenIdle: true,
- handler: me.onPlay,
- scope: me,
- menu: [
- speed('Qarter Speed (0.25x)', 0.25),
- speed('Half Speed (0.5x)', 0.5),
- speed('3/4 Speed (0.75x)', 0.75),
- '-',
- speed('Recorded Speed (1x)', 1),
- speed('Double Speed (2x)', 2),
- speed('Quad Speed (4x)', 4),
- '-',
- speed('Full Speed', 1000)
- ]
- },
- {
- text: 'Clear',
- whenIdle: true,
- handler: me.onClear,
- scope: me
- },
- '->',
- {
- text: 'Stop',
- whenActive: true,
- disabled: true,
- handler: me.onStop,
- scope: me
- }
- ];
- events = me.attachTo && me.attachTo.testEvents;
- me.items = [
- {
- xtype: 'textarea',
- itemId: 'eventView',
- fieldStyle: 'font-family: monospace',
- selectOnFocus: true,
- emptyText: 'Events go here!',
- value: events ? me.stringifyEvents(events) : '',
- scrollToBottom: function() {
- var inputEl = this.inputEl.dom;
- inputEl.scrollTop = inputEl.scrollHeight;
- }
- }
- ];
- me.fbar = [
- {
- xtype: 'tbtext',
- text: 'Attached To: ' + (me.attachTo && me.attachTo.location.href)
- }
- ];
- me.callParent();
- },
- makeRecordButtonMenu: function() {
- var ret = [],
- subs = {},
- eventsToRec = this.recorder.eventsToRecord,
- ignoredEvents = this.eventsToIgnore;
- Ext.Object.each(eventsToRec, function(name, value) {
- var sub = subs[value.kind];
- if (!sub) {
- subs[value.kind] = sub = [];
- ret.push({
- text: value.kind,
- menu: sub
- });
- }
- sub.push({
- text: name,
- checked: true,
- handler: function(menuItem) {
- if (menuItem.checked) {
- eventsToRec[name] = value;
- } else {
- delete eventsToRec[name];
- }
- }
- });
- if (ignoredEvents[name]) {
- sub[sub.length - 1].checked = false;
- Ext.defer(function() {
- delete eventsToRec[name];
- }, 1);
- }
- });
- function less(lhs, rhs) {
- return (lhs.text < rhs.text) ? -1 : ((rhs.text < lhs.text) ? 1 : 0);
- }
- ret.sort(less);
- Ext.Array.each(ret, function(sub) {
- sub.menu.sort(less);
- });
- return ret;
- },
- getEventView: function() {
- return this.down('#eventView');
- },
- onClear: function() {
- var view = this.getEventView();
- view.setValue('');
- },
- onPlay: function() {
- var me = this,
- view = me.getEventView(),
- events = view.getValue();
- if (events) {
- events = Ext.decode(events);
- if (events.length) {
- me.player = Ext.create('Ext.ux.event.Player', {
- attachTo: window.opener,
- eventQueue: events,
- speed: me.playSpeed,
- listeners: {
- stop: me.onPlayStop,
- scope: me
- }
- });
- me.player.start();
- me.syncBtnUI();
- }
- }
- },
- onPlayStop: function() {
- this.player = null;
- this.syncBtnUI();
- },
- onPlaySpeed: function(menuitem) {
- this.playSpeed = menuitem.speed;
- },
- onRecord: function() {
- this.recorder.start();
- this.syncBtnUI();
- },
- onStop: function() {
- var me = this;
- if (me.player) {
- me.player.stop();
- me.player = null;
- } else {
- me.recorder.stop();
- }
- me.syncBtnUI();
- me.updateEvents();
- },
- syncBtnUI: function() {
- var me = this,
- idle = !me.player && !me.recorder.active,
- view;
- Ext.each(me.query('[whenIdle]'), function(btn) {
- btn.setDisabled(!idle);
- });
- Ext.each(me.query('[whenActive]'), function(btn) {
- btn.setDisabled(idle);
- });
- view = me.getEventView();
- view.setReadOnly(!idle);
- },
- stringifyEvents: function(events) {
- var line,
- lines = [];
- Ext.each(events, function(ev) {
- line = [];
- Ext.Object.each(ev, function(name, value) {
- if (line.length) {
- line.push(', ');
- } else {
- line.push(' { ');
- }
- line.push(name, ': ');
- line.push(Ext.encode(value));
- });
- line.push(' }');
- lines.push(line.join(''));
- });
- return '[\n' + lines.join(',\n') + '\n]';
- },
- updateEvents: function() {
- var me = this,
- text = me.stringifyEvents(me.recorder.getRecordedEvents()),
- view = me.getEventView();
- view.setValue(text);
- view.scrollToBottom();
- }
- });
- /**
- * A control that allows selection of multiple items in a list.
- */
- Ext.define('Ext.ux.form.MultiSelect', {
- extend: 'Ext.form.FieldContainer',
- mixins: [
- 'Ext.util.StoreHolder',
- 'Ext.form.field.Field'
- ],
- alternateClassName: 'Ext.ux.Multiselect',
- alias: [
- 'widget.multiselectfield',
- 'widget.multiselect'
- ],
- requires: [
- 'Ext.panel.Panel',
- 'Ext.view.BoundList',
- 'Ext.layout.container.Fit'
- ],
- uses: [
- 'Ext.view.DragZone',
- 'Ext.view.DropZone'
- ],
- layout: 'anchor',
- /**
- * @cfg {String} [dragGroup=""] The ddgroup name for the MultiSelect DragZone.
- */
- /**
- * @cfg {String} [dropGroup=""] The ddgroup name for the MultiSelect DropZone.
- */
- /**
- * @cfg {String} [title=""] A title for the underlying panel.
- */
- /**
- * @cfg {Boolean} [ddReorder=false] Whether the items in the MultiSelect list are drag/drop
- * reorderable.
- */
- ddReorder: false,
- /**
- * @cfg {Object/Array} tbar An optional toolbar to be inserted at the top of the control's
- * selection list. This can be a {@link Ext.toolbar.Toolbar} object, a toolbar config,
- * or an array of buttons/button configs to be added to the toolbar.
- * See {@link Ext.panel.Panel#tbar}.
- */
- /**
- * @cfg {String} [appendOnly=false] `true` if the list should only allow append drops
- * when drag/drop is enabled. This is useful for lists which are sorted.
- */
- appendOnly: false,
- /**
- * @cfg {String} [displayField="text"] Name of the desired display field in the dataset.
- */
- displayField: 'text',
- /**
- * @cfg {String} [valueField="text"] Name of the desired value field in the dataset.
- */
- /**
- * @cfg {Boolean} [allowBlank=true] `false` to require at least one item in the list
- * to be selected, `true` to allow no selection.
- */
- allowBlank: true,
- /**
- * @cfg {Number} [minSelections=0] Minimum number of selections allowed.
- */
- minSelections: 0,
- /**
- * @cfg {Number} [maxSelections=Number.MAX_VALUE] Maximum number of selections allowed.
- */
- maxSelections: Number.MAX_VALUE,
- /**
- * @cfg {String} [blankText="This field is required"] Default text displayed when the control
- * contains no items.
- */
- blankText: 'This field is required',
- /**
- * @cfg {String} [minSelectionsText="Minimum {0}item(s) required"]
- * Validation message displayed when {@link #minSelections} is not met.
- * The {0} token will be replaced by the value of {@link #minSelections}.
- */
- minSelectionsText: 'Minimum {0} item(s) required',
- /**
- * @cfg {String} [maxSelectionsText="Maximum {0}item(s) allowed"]
- * Validation message displayed when {@link #maxSelections} is not met
- * The {0} token will be replaced by the value of {@link #maxSelections}.
- */
- maxSelectionsText: 'Maximum {0} item(s) required',
- /**
- * @cfg {String} [delimiter=","] The string used to delimit the selected values when
- * {@link #getSubmitValue submitting} the field as part of a form. If you wish to have
- * the selected values submitted as separate parameters rather than a single
- * delimited parameter, set this to `null`.
- */
- delimiter: ',',
- /**
- * @cfg {String} [dragText="{0} Item{1}"] The text to show while dragging items.
- * {0} will be replaced by the number of items. {1} will be replaced by the plural
- * form if there is more than 1 item.
- */
- dragText: '{0} Item{1}',
- /**
- * @cfg {Ext.data.Store/Array} store The data source to which this MultiSelect is bound
- * (defaults to `undefined`).
- * Acceptable values for this property are:
- * <div class="mdetail-params"><ul>
- * <li><b>any {@link Ext.data.Store Store} subclass</b></li>
- * <li><b>an Array</b> : Arrays will be converted to a {@link Ext.data.ArrayStore} internally.
- * <div class="mdetail-params"><ul>
- * <li><b>1-dimensional array</b> : (e.g., <tt>['Foo','Bar']</tt>)<div class="sub-desc">
- * A 1-dimensional array will automatically be expanded (each array item will be the combo
- * {@link #valueField value} and {@link #displayField text})</div></li>
- * <li><b>2-dimensional array</b> : (e.g., `[['f','Foo'],['b','Bar']]`)<div class="sub-desc">
- * For a multi-dimensional array, the value in index 0 of each item will be assumed to be
- * the combo {@link #valueField value}, while the value at index 1 is assumed to be the combo
- * {@link #displayField text}.
- * </div></li></ul></div></li></ul></div>
- */
- ignoreSelectChange: 0,
- /**
- * @cfg {Object} listConfig
- * An optional set of configuration properties that will be passed to the
- * {@link Ext.view.BoundList}'s constructor. Any configuration that is valid for BoundList
- * can be included.
- */
- /**
- * @cfg {Number} [pageSize=10] The number of items to advance on pageUp and pageDown
- */
- pageSize: 10,
- initComponent: function() {
- var me = this;
- me.items = me.setupItems();
- me.bindStore(me.store, true);
- me.callParent();
- me.initField();
- },
- setupItems: function() {
- var me = this;
- me.boundList = new Ext.view.BoundList(Ext.apply({
- anchor: 'none 100%',
- border: 1,
- multiSelect: true,
- store: me.store,
- displayField: me.displayField,
- disabled: me.disabled,
- tabIndex: 0,
- navigationModel: {
- type: 'default'
- }
- }, me.listConfig));
- me.boundList.getNavigationModel().addKeyBindings({
- pageUp: me.onKeyPageUp,
- pageDown: me.onKeyPageDown,
- scope: me
- });
- me.boundList.getSelectionModel().on('selectionchange', me.onSelectChange, me);
- // Boundlist expects a reference to its pickerField for when an item is selected
- // (see Boundlist#onItemClick).
- me.boundList.pickerField = me;
- // Only need to wrap the BoundList in a Panel if we have a title.
- if (!me.title) {
- return me.boundList;
- }
- // Wrap to add a title
- me.boundList.border = false;
- return {
- xtype: 'panel',
- isAriaRegion: false,
- border: true,
- anchor: 'none 100%',
- layout: 'anchor',
- title: me.title,
- tbar: me.tbar,
- items: me.boundList
- };
- },
- onSelectChange: function(selModel, selections) {
- if (!this.ignoreSelectChange) {
- this.setValue(selections);
- }
- },
- getSelected: function() {
- return this.boundList.getSelectionModel().getSelection();
- },
- // compare array values
- isEqual: function(v1, v2) {
- var fromArray = Ext.Array.from,
- i = 0,
- len;
- v1 = fromArray(v1);
- v2 = fromArray(v2);
- len = v1.length;
- if (len !== v2.length) {
- return false;
- }
- for (; i < len; i++) {
- if (v2[i] !== v1[i]) {
- return false;
- }
- }
- return true;
- },
- afterRender: function() {
- var me = this,
- boundList, scrollable, records, panel;
- me.callParent();
- boundList = me.boundList;
- scrollable = boundList && boundList.getScrollable();
- if (me.selectOnRender) {
- records = me.getRecordsForValue(me.value);
- if (records.length) {
- ++me.ignoreSelectChange;
- boundList.getSelectionModel().select(records);
- --me.ignoreSelectChange;
- }
- delete me.toSelect;
- }
- if (me.ddReorder && !me.dragGroup && !me.dropGroup) {
- me.dragGroup = me.dropGroup = 'MultiselectDD-' + Ext.id();
- }
- if (me.draggable || me.dragGroup) {
- me.dragZone = Ext.create('Ext.view.DragZone', {
- view: boundList,
- ddGroup: me.dragGroup,
- dragText: me.dragText,
- containerScroll: !!scrollable,
- scrollEl: scrollable && scrollable.getElement()
- });
- }
- if (me.droppable || me.dropGroup) {
- me.dropZone = Ext.create('Ext.view.DropZone', {
- view: boundList,
- ddGroup: me.dropGroup,
- handleNodeDrop: function(data, dropRecord, position) {
- var view = this.view,
- store = view.getStore(),
- records = data.records,
- index;
- // remove the Models from the source Store
- data.view.store.remove(records);
- index = store.indexOf(dropRecord);
- if (position === 'after') {
- index++;
- }
- store.insert(index, records);
- view.getSelectionModel().select(records);
- me.fireEvent('drop', me, records);
- }
- });
- }
- panel = me.down('panel');
- if (panel && boundList) {
- boundList.ariaEl.dom.setAttribute('aria-labelledby', panel.header.id + '-title-textEl');
- }
- },
- onKeyPageUp: function(e) {
- var me = this,
- pageSize = me.pageSize,
- boundList = me.boundList,
- nm = boundList.getNavigationModel(),
- oldIdx, newIdx;
- oldIdx = nm.recordIndex;
- // Unlike up arrow, pgUp does not wrap but goes to the first item
- newIdx = oldIdx > pageSize ? oldIdx - pageSize : 0;
- nm.setPosition(newIdx, e);
- },
- onKeyPageDown: function(e) {
- var me = this,
- pageSize = me.pageSize,
- boundList = me.boundList,
- nm = boundList.getNavigationModel(),
- count, oldIdx, newIdx;
- count = boundList.getStore().getCount();
- oldIdx = nm.recordIndex;
- // Unlike down arrow, pgDown does not wrap but goes to the last item
- newIdx = oldIdx < (count - pageSize) ? oldIdx + pageSize : count - 1;
- nm.setPosition(newIdx, e);
- },
- isValid: function() {
- var me = this,
- disabled = me.disabled,
- validate = me.forceValidation || !disabled;
- return validate ? me.validateValue(me.value) : disabled;
- },
- validateValue: function(value) {
- var me = this,
- errors = me.getErrors(value),
- isValid = Ext.isEmpty(errors);
- if (!me.preventMark) {
- if (isValid) {
- me.clearInvalid();
- } else {
- me.markInvalid(errors);
- }
- }
- return isValid;
- },
- markInvalid: function(errors) {
- // Save the message and fire the 'invalid' event
- var me = this,
- oldMsg = me.getActiveError();
- me.setActiveErrors(Ext.Array.from(errors));
- if (oldMsg !== me.getActiveError()) {
- me.updateLayout();
- }
- },
- /**
- * Clear any invalid styles/messages for this field.
- *
- * __Note:__ this method does not cause the Field's {@link #validate} or {@link #isValid}
- * methods to return `true` if the value does not _pass_ validation. So simply clearing
- * a field's errors will not necessarily allow submission of forms submitted with the
- * {@link Ext.form.action.Submit#clientValidation} option set.
- */
- clearInvalid: function() {
- // Clear the message and fire the 'valid' event
- var me = this,
- hadError = me.hasActiveError();
- me.unsetActiveError();
- if (hadError) {
- me.updateLayout();
- }
- },
- getSubmitData: function() {
- var me = this,
- data = null,
- val;
- if (!me.disabled && me.submitValue && !me.isFileUpload()) {
- val = me.getSubmitValue();
- if (val !== null) {
- data = {};
- data[me.getName()] = val;
- }
- }
- return data;
- },
- /**
- * Returns the value that would be included in a standard form submit for this field.
- *
- * @return {String} The value to be submitted, or `null`.
- */
- getSubmitValue: function() {
- var me = this,
- delimiter = me.delimiter,
- val = me.getValue();
- return Ext.isString(delimiter) ? val.join(delimiter) : val;
- },
- getValue: function() {
- return this.value || [];
- },
- getRecordsForValue: function(value) {
- var me = this,
- records = [],
- all = me.store.getRange(),
- valueField = me.valueField,
- i = 0,
- allLen = all.length,
- rec, j, valueLen;
- for (valueLen = value.length; i < valueLen; ++i) {
- for (j = 0; j < allLen; ++j) {
- rec = all[j];
- if (rec.get(valueField) === value[i]) {
- records.push(rec);
- }
- }
- }
- return records;
- },
- setupValue: function(value) {
- var delimiter = this.delimiter,
- valueField = this.valueField,
- i = 0,
- out, len, item;
- if (Ext.isDefined(value)) {
- if (delimiter && Ext.isString(value)) {
- value = value.split(delimiter);
- } else if (!Ext.isArray(value)) {
- value = [
- value
- ];
- }
- for (len = value.length; i < len; ++i) {
- item = value[i];
- if (item && item.isModel) {
- value[i] = item.get(valueField);
- }
- }
- out = Ext.Array.unique(value);
- } else {
- out = [];
- }
- return out;
- },
- setValue: function(value) {
- var me = this,
- selModel = me.boundList.getSelectionModel(),
- store = me.store;
- // Store not loaded yet - we cannot set the value
- if (!store.getCount()) {
- store.on({
- load: Ext.Function.bind(me.setValue, me, [
- value
- ]),
- single: true
- });
- return;
- }
- value = me.setupValue(value);
- me.mixins.field.setValue.call(me, value);
- if (me.rendered) {
- ++me.ignoreSelectChange;
- selModel.deselectAll();
- if (value.length) {
- selModel.select(me.getRecordsForValue(value));
- }
- --me.ignoreSelectChange;
- } else {
- me.selectOnRender = true;
- }
- },
- clearValue: function() {
- this.setValue([]);
- },
- onEnable: function() {
- var list = this.boundList;
- this.callParent();
- if (list) {
- list.enable();
- }
- },
- onDisable: function() {
- var list = this.boundList;
- this.callParent();
- if (list) {
- list.disable();
- }
- },
- getErrors: function(value) {
- var me = this,
- format = Ext.String.format,
- errors = [],
- numSelected;
- value = Ext.Array.from(value || me.getValue());
- numSelected = value.length;
- if (!me.allowBlank && numSelected < 1) {
- errors.push(me.blankText);
- }
- if (numSelected < me.minSelections) {
- errors.push(format(me.minSelectionsText, me.minSelections));
- }
- if (numSelected > me.maxSelections) {
- errors.push(format(me.maxSelectionsText, me.maxSelections));
- }
- return errors;
- },
- doDestroy: function() {
- var me = this;
- me.bindStore(null);
- Ext.destroy(me.dragZone, me.dropZone, me.keyNav);
- me.callParent();
- },
- onBindStore: function(store) {
- var me = this,
- boundList = this.boundList;
- if (store.autoCreated) {
- me.resolveDisplayField();
- }
- if (!Ext.isDefined(me.valueField)) {
- me.valueField = me.displayField;
- }
- if (boundList) {
- boundList.bindStore(store);
- }
- },
- /**
- * Applies auto-created store fields to field and boundlist
- * @private
- */
- resolveDisplayField: function() {
- var me = this,
- boundList = me.boundList,
- store = me.getStore();
- me.valueField = me.displayField = 'field1';
- if (!store.expanded) {
- me.displayField = 'field2';
- }
- if (boundList) {
- boundList.setDisplayField(me.displayField);
- }
- }
- });
- /*
- * Note that this control will most likely remain as an example, and not as a core Ext form
- * control. However, the API will be changing in a future release and so should not yet be
- * treated as a final, stable API at this time.
- */
- /**
- * A control that allows selection of between two Ext.ux.form.MultiSelect controls.
- */
- Ext.define('Ext.ux.form.ItemSelector', {
- extend: 'Ext.ux.form.MultiSelect',
- alias: [
- 'widget.itemselectorfield',
- 'widget.itemselector'
- ],
- alternateClassName: [
- 'Ext.ux.ItemSelector'
- ],
- requires: [
- 'Ext.button.Button',
- 'Ext.ux.form.MultiSelect'
- ],
- /**
- * @cfg {Boolean} [hideNavIcons=false] True to hide the navigation icons
- */
- hideNavIcons: false,
- /**
- * @cfg {Array} buttons Defines the set of buttons that should be displayed in between
- * the ItemSelector fields. Defaults to `['top', 'up', 'add', 'remove', 'down', 'bottom']`.
- * These names are used to build the button CSS class names, and to look up the button text
- * labels in {@link #buttonsText}. This can be overridden with a custom Array to change
- * which buttons are displayed or their order.
- */
- buttons: [
- 'top',
- 'up',
- 'add',
- 'remove',
- 'down',
- 'bottom'
- ],
- /**
- * @cfg {Object} buttonsText The tooltips for the {@link #buttons}.
- * Labels for buttons.
- */
- buttonsText: {
- top: "Move to Top",
- up: "Move Up",
- add: "Add to Selected",
- remove: "Remove from Selected",
- down: "Move Down",
- bottom: "Move to Bottom"
- },
- layout: {
- type: 'hbox',
- align: 'stretch'
- },
- ariaRole: 'group',
- initComponent: function() {
- var me = this;
- me.ddGroup = me.id + '-dd';
- me.ariaRenderAttributes = me.ariaRenderAttributes || {};
- me.ariaRenderAttributes['aria-labelledby'] = me.id + '-labelEl';
- me.callParent();
- // bindStore must be called after the fromField has been created because
- // it copies records from our configured Store into the fromField's Store
- me.bindStore(me.store);
- },
- createList: function(title) {
- var me = this;
- return Ext.create('Ext.ux.form.MultiSelect', {
- // We don't want the multiselects themselves to act like fields,
- // so override these methods to prevent them from including
- // any of their values
- submitValue: false,
- getSubmitData: function() {
- return null;
- },
- getModelData: function() {
- return null;
- },
- flex: 1,
- dragGroup: me.ddGroup,
- dropGroup: me.ddGroup,
- title: title,
- store: {
- model: me.store.model,
- data: []
- },
- displayField: me.displayField,
- valueField: me.valueField,
- disabled: me.disabled,
- listeners: {
- boundList: {
- scope: me,
- itemdblclick: me.onItemDblClick,
- drop: me.syncValue
- }
- }
- });
- },
- setupItems: function() {
- var me = this;
- me.fromField = me.createList(me.fromTitle);
- me.toField = me.createList(me.toTitle);
- return [
- me.fromField,
- {
- xtype: 'toolbar',
- margin: '0 4',
- padding: 0,
- layout: {
- type: 'vbox',
- pack: 'center'
- },
- items: me.createButtons()
- },
- me.toField
- ];
- },
- createButtons: function() {
- var me = this,
- buttons = [];
- if (!me.hideNavIcons) {
- Ext.Array.forEach(me.buttons, function(name) {
- buttons.push({
- xtype: 'button',
- ui: 'default',
- tooltip: me.buttonsText[name],
- ariaLabel: me.buttonsText[name],
- handler: me['on' + Ext.String.capitalize(name) + 'BtnClick'],
- cls: Ext.baseCSSPrefix + 'form-itemselector-btn',
- iconCls: Ext.baseCSSPrefix + 'form-itemselector-' + name,
- navBtn: true,
- scope: me,
- margin: '4 0 0 0'
- });
- });
- }
- return buttons;
- },
- /**
- * Get the selected records from the specified list.
- *
- * Records will be returned *in store order*, not in order of selection.
- * @param {Ext.view.BoundList} list The list to read selections from.
- * @return {Ext.data.Model[]} The selected records in store order.
- *
- */
- getSelections: function(list) {
- var store = list.getStore();
- return Ext.Array.sort(list.getSelectionModel().getSelection(), function(a, b) {
- a = store.indexOf(a);
- b = store.indexOf(b);
- if (a < b) {
- return -1;
- } else if (a > b) {
- return 1;
- }
- return 0;
- });
- },
- onTopBtnClick: function() {
- var list = this.toField.boundList,
- store = list.getStore(),
- selected = this.getSelections(list);
- store.suspendEvents();
- store.remove(selected, true);
- store.insert(0, selected);
- store.resumeEvents();
- list.refresh();
- this.syncValue();
- list.getSelectionModel().select(selected);
- },
- onBottomBtnClick: function() {
- var list = this.toField.boundList,
- store = list.getStore(),
- selected = this.getSelections(list);
- store.suspendEvents();
- store.remove(selected, true);
- store.add(selected);
- store.resumeEvents();
- list.refresh();
- this.syncValue();
- list.getSelectionModel().select(selected);
- },
- onUpBtnClick: function() {
- var list = this.toField.boundList,
- store = list.getStore(),
- selected = this.getSelections(list),
- rec,
- i = 0,
- len = selected.length,
- index = 0;
- // Move each selection up by one place if possible
- store.suspendEvents();
- for (; i < len; ++i , index++) {
- rec = selected[i];
- index = Math.max(index, store.indexOf(rec) - 1);
- store.remove(rec, true);
- store.insert(index, rec);
- }
- store.resumeEvents();
- list.refresh();
- this.syncValue();
- list.getSelectionModel().select(selected);
- },
- onDownBtnClick: function() {
- var list = this.toField.boundList,
- store = list.getStore(),
- selected = this.getSelections(list),
- rec,
- i = selected.length - 1,
- index = store.getCount() - 1;
- // Move each selection down by one place if possible
- store.suspendEvents();
- for (; i > -1; --i , index--) {
- rec = selected[i];
- index = Math.min(index, store.indexOf(rec) + 1);
- store.remove(rec, true);
- store.insert(index, rec);
- }
- store.resumeEvents();
- list.refresh();
- this.syncValue();
- list.getSelectionModel().select(selected);
- },
- onAddBtnClick: function() {
- var me = this,
- selected = me.getSelections(me.fromField.boundList);
- me.moveRec(true, selected);
- me.toField.boundList.getSelectionModel().select(selected);
- },
- onRemoveBtnClick: function() {
- var me = this,
- selected = me.getSelections(me.toField.boundList);
- me.moveRec(false, selected);
- me.fromField.boundList.getSelectionModel().select(selected);
- },
- moveRec: function(add, recs) {
- var me = this,
- fromField = me.fromField,
- toField = me.toField,
- fromStore = add ? fromField.store : toField.store,
- toStore = add ? toField.store : fromField.store;
- fromStore.suspendEvents();
- toStore.suspendEvents();
- fromStore.remove(recs);
- toStore.add(recs);
- fromStore.resumeEvents();
- toStore.resumeEvents();
- // If the list item was focused when moved (e.g. via double-click)
- // then removing it will cause the focus to be thrown back to the
- // document body. Which might disrupt things if ItemSelector is
- // contained by a floating thingie like a Menu.
- // Focusing the list itself will prevent that.
- if (fromField.boundList.containsFocus) {
- fromField.boundList.focus();
- }
- fromField.boundList.refresh();
- toField.boundList.refresh();
- me.syncValue();
- },
- // Synchronizes the submit value with the current state of the toStore
- syncValue: function() {
- var me = this;
- me.mixins.field.setValue.call(me, me.setupValue(me.toField.store.getRange()));
- },
- onItemDblClick: function(view, rec) {
- this.moveRec(view === this.fromField.boundList, rec);
- },
- setValue: function(value) {
- var me = this,
- fromField = me.fromField,
- toField = me.toField,
- fromStore = fromField.store,
- toStore = toField.store,
- selected;
- // Wait for from store to be loaded
- if (!me.fromStorePopulated) {
- me.fromField.store.on({
- load: Ext.Function.bind(me.setValue, me, [
- value
- ]),
- single: true
- });
- return;
- }
- value = me.setupValue(value);
- me.mixins.field.setValue.call(me, value);
- selected = me.getRecordsForValue(value);
- // Clear both left and right Stores.
- // Both stores must not fire events during this process.
- fromStore.suspendEvents();
- toStore.suspendEvents();
- fromStore.removeAll();
- toStore.removeAll();
- // Reset fromStore
- me.populateFromStore(me.store);
- // Copy selection across to toStore
- Ext.Array.forEach(selected, function(rec) {
- // In the from store, move it over
- if (fromStore.indexOf(rec) > -1) {
- fromStore.remove(rec);
- }
- toStore.add(rec);
- });
- // Stores may now fire events
- fromStore.resumeEvents();
- toStore.resumeEvents();
- // Refresh both sides and then update the app layout
- Ext.suspendLayouts();
- fromField.boundList.refresh();
- toField.boundList.refresh();
- Ext.resumeLayouts(true);
- },
- onBindStore: function(store, initial) {
- var me = this,
- fromField = me.fromField,
- toField = me.toField;
- if (fromField) {
- fromField.store.removeAll();
- toField.store.removeAll();
- if (store.autoCreated) {
- fromField.resolveDisplayField();
- toField.resolveDisplayField();
- me.resolveDisplayField();
- }
- if (!Ext.isDefined(me.valueField)) {
- me.valueField = me.displayField;
- }
- // Add everything to the from field as soon as the Store is loaded
- if (store.getCount()) {
- me.populateFromStore(store);
- } else {
- me.store.on('load', me.populateFromStore, me);
- }
- }
- },
- populateFromStore: function(store) {
- var fromStore = this.fromField.store;
- // Flag set when the fromStore has been loaded
- this.fromStorePopulated = true;
- fromStore.add(store.getRange());
- // setValue waits for the from Store to be loaded
- fromStore.fireEvent('load', fromStore);
- },
- onEnable: function() {
- var me = this;
- me.callParent();
- me.fromField.enable();
- me.toField.enable();
- Ext.Array.forEach(me.query('[navBtn]'), function(btn) {
- btn.enable();
- });
- },
- onDisable: function() {
- var me = this;
- me.callParent();
- me.fromField.disable();
- me.toField.disable();
- Ext.Array.forEach(me.query('[navBtn]'), function(btn) {
- btn.disable();
- });
- },
- doDestroy: function() {
- this.bindStore(null);
- this.callParent();
- }
- });
- /**
- *
- */
- Ext.define('Ext.ux.form.SearchField', {
- extend: 'Ext.form.field.Text',
- alias: 'widget.searchfield',
- triggers: {
- clear: {
- weight: 0,
- cls: Ext.baseCSSPrefix + 'form-clear-trigger',
- hidden: true,
- handler: 'onClearClick',
- scope: 'this'
- },
- search: {
- weight: 1,
- cls: Ext.baseCSSPrefix + 'form-search-trigger',
- handler: 'onSearchClick',
- scope: 'this'
- }
- },
- hasSearch: false,
- paramName: 'query',
- initComponent: function() {
- var me = this,
- store = me.store,
- proxy;
- me.callParent(arguments);
- me.on('specialkey', function(f, e) {
- if (e.getKey() === e.ENTER) {
- me.onSearchClick();
- }
- });
- if (!store || !store.isStore) {
- store = me.store = Ext.data.StoreManager.lookup(store);
- }
- // We're going to use filtering
- store.setRemoteFilter(true);
- // Set up the proxy to encode the filter in the simplest way as a name/value pair
- proxy = me.store.getProxy();
- proxy.setFilterParam(me.paramName);
- proxy.encodeFilters = function(filters) {
- return filters[0].getValue();
- };
- },
- onClearClick: function() {
- var me = this,
- activeFilter = me.activeFilter;
- if (activeFilter) {
- me.setValue('');
- me.store.getFilters().remove(activeFilter);
- me.activeFilter = null;
- me.getTrigger('clear').hide();
- me.updateLayout();
- }
- },
- onSearchClick: function() {
- var me = this,
- value = me.getValue();
- if (value.length > 0) {
- // Param name is ignored here since we use custom encoding in the proxy.
- // id is used by the Store to replace any previous filter
- me.activeFilter = new Ext.util.Filter({
- property: me.paramName,
- value: value
- });
- me.store.getFilters().add(me.activeFilter);
- me.getTrigger('clear').show();
- me.updateLayout();
- }
- }
- });
- /**
- * A small grid nested within a parent grid's row.
- *
- * See the [Kitchen Sink](http://dev.sencha.com/extjs/5.0.1/examples/kitchensink/#customer-grid)
- * for example usage.
- */
- Ext.define('Ext.ux.grid.SubTable', {
- extend: 'Ext.grid.plugin.RowExpander',
- alias: 'plugin.subtable',
- /* eslint-disable indent */
- rowBodyTpl: [
- '<table class="' + Ext.baseCSSPrefix + 'grid-subtable">',
- '{%',
- 'this.owner.renderTable(out, values);',
- '%}',
- '</table>'
- ],
- /* eslint-enable indent */
- init: function(grid) {
- var me = this,
- columns = me.columns,
- len, i, columnCfg;
- me.callParent(arguments);
- me.columns = [];
- if (columns) {
- for (i = 0 , len = columns.length; i < len; ++i) {
- // Don't register with the component manager, we create them to use
- // their rendering smarts, but don't want to treat them as real components
- columnCfg = Ext.apply({
- preventRegister: true
- }, columns[i]);
- columnCfg.xtype = columnCfg.xtype || 'gridcolumn';
- me.columns.push(Ext.widget(columnCfg));
- }
- }
- },
- destroy: function() {
- var columns = this.columns,
- len, i;
- if (columns) {
- for (i = 0 , len = columns.length; i < len; ++i) {
- columns[i].destroy();
- }
- }
- this.columns = null;
- this.callParent();
- },
- getRowBodyFeatureData: function(record, idx, rowValues) {
- this.callParent(arguments);
- rowValues.rowBodyCls += ' ' + Ext.baseCSSPrefix + 'grid-subtable-row';
- },
- renderTable: function(out, rowValues) {
- var me = this,
- columns = me.columns,
- numColumns = columns.length,
- associatedRecords = me.getAssociatedRecords(rowValues.record),
- recCount = associatedRecords.length,
- rec, column, i, j, value;
- out.push('<thead>');
- for (j = 0; j < numColumns; j++) {
- out.push('<th class="' + Ext.baseCSSPrefix + 'grid-subtable-header">', columns[j].text, '</th>');
- }
- out.push('</thead><tbody>');
- for (i = 0; i < recCount; i++) {
- rec = associatedRecords[i];
- out.push('<tr>');
- for (j = 0; j < numColumns; j++) {
- column = columns[j];
- value = rec.get(column.dataIndex);
- if (column.renderer && column.renderer.call) {
- value = column.renderer.call(column.scope || me, value, {}, rec);
- }
- out.push('<td class="' + Ext.baseCSSPrefix + 'grid-subtable-cell"');
- if (column.width != null) {
- out.push(' style="width:' + column.width + 'px"');
- }
- out.push('><div class="' + Ext.baseCSSPrefix + 'grid-cell-inner">', value, '</div></td>');
- }
- out.push('</tr>');
- }
- out.push('</tbody>');
- },
- getRowBodyContentsFn: function(rowBodyTpl) {
- var me = this;
- return function(rowValues) {
- rowBodyTpl.owner = me;
- return rowBodyTpl.applyTemplate(rowValues);
- };
- },
- getAssociatedRecords: function(record) {
- return record[this.association]().getRange();
- }
- });
- /**
- * A Grid which creates itself from an existing HTML table element.
- */
- Ext.define('Ext.ux.grid.TransformGrid', {
- extend: 'Ext.grid.Panel',
- /**
- * Creates the grid from HTML table element.
- * @param {String/HTMLElement/Ext.Element} table The table element from which this grid
- * will be created - The table MUST have some type of size defined for the grid to fill.
- * The container will be automatically set to position relative if it isn't already.
- * @param {Object} [config] A config object that sets properties on this grid and has two
- * additional (optional) properties: fields and columns which allow for customizing data fields
- * and columns for this grid.
- */
- constructor: function(table, config) {
- config = Ext.apply({}, config);
- table = this.table = Ext.get(table);
- // eslint-disable-next-line vars-on-top
- var configFields = config.fields || [],
- configColumns = config.columns || [],
- fields = [],
- cols = [],
- headers = table.query("thead th"),
- i = 0,
- len = headers.length,
- data = table.dom,
- width, height, col, text, name;
- for (; i < len; ++i) {
- col = headers[i];
- text = col.innerHTML;
- name = 'tcol-' + i;
- fields.push(Ext.applyIf(configFields[i] || {}, {
- name: name,
- mapping: 'td:nth(' + (i + 1) + ')/@innerHTML'
- }));
- cols.push(Ext.applyIf(configColumns[i] || {}, {
- text: text,
- dataIndex: name,
- width: col.offsetWidth,
- tooltip: col.title,
- sortable: true
- }));
- }
- if (config.width) {
- width = config.width;
- } else {
- width = table.getWidth() + 1;
- }
- if (config.height) {
- height = config.height;
- }
- Ext.applyIf(config, {
- store: {
- data: data,
- fields: fields,
- proxy: {
- type: 'memory',
- reader: {
- record: 'tbody tr',
- type: 'xml'
- }
- }
- },
- columns: cols,
- width: width,
- height: height
- });
- this.callParent([
- config
- ]);
- if (config.remove !== false) {
- // Don't use table.remove() as that destroys the row/cell data in the table in
- // IE6-7 so it cannot be read by the data reader.
- data.parentNode.removeChild(data);
- }
- },
- doDestroy: function() {
- this.table.remove();
- this.tabl = null;
- this.callParent();
- }
- });
- /**
- * This plugin ensures that its associated grid or tree always has a selection record. The
- * only exception is, of course, when there are no records in the store.
- * @since 6.0.2
- */
- Ext.define('Ext.ux.grid.plugin.AutoSelector', {
- extend: 'Ext.plugin.Abstract',
- alias: 'plugin.gridautoselector',
- config: {
- store: null
- },
- init: function(grid) {
- var me = this;
- //<debug>
- if (!grid.isXType('tablepanel')) {
- Ext.raise('The gridautoselector plugin is designed only for grids and trees');
- }
- //</debug>
- me.grid = grid;
- me.watchGrid();
- grid.on({
- reconfigure: me.watchGrid,
- scope: me
- });
- },
- destroy: function() {
- this.setStore(null);
- this.grid = null;
- this.callParent();
- },
- ensureSelection: function() {
- var grid = this.grid,
- store = grid.getStore(),
- selection;
- if (store.getCount()) {
- selection = grid.getSelection();
- if (!selection || !selection.length) {
- grid.getSelectionModel().select(0);
- }
- }
- },
- watchGrid: function() {
- this.setStore(this.grid.getStore());
- this.ensureSelection();
- },
- updateStore: function(store) {
- var me = this;
- Ext.destroy(me.storeListeners);
- me.storeListeners = store && store.on({
- // We could go from 0 records to 1+ records... now we can select one!
- add: me.ensureSelection,
- // We might remove the selected record...
- remove: me.ensureSelection,
- destroyable: true,
- scope: me
- });
- }
- });
- /**
- * A simple grid-like layout for proportionally dividing container space and allocating it
- * to each item. All items in this layout are given one or more percentage sizes and CSS
- * `float:left` is used to provide the wrapping.
- *
- * To select which of the percentage sizes an item uses, this layout adds a viewport
- * {@link #states size-dependent} class name to the container. The style sheet must
- * provide the rules to select the desired size using the {@link #responsivecolumn-item}
- * mixin.
- *
- * For example, a panel in a responsive column layout might add the following styles:
- *
- * .my-panel {
- * // consume 50% of the available space inside the container by default
- * @include responsivecolumn-item(50%);
- *
- * .x-responsivecolumn-small & {
- * // consume 100% of available space in "small" mode
- * // (viewport width < 1000 by default)
- * @include responsivecolumn-item(100%);
- * }
- * }
- *
- * Alternatively, instead of targeting specific panels in CSS, you can create reusable
- * classes:
- *
- * .big-50 {
- * // consume 50% of the available space inside the container by default
- * @include responsivecolumn-item(50%);
- * }
- *
- * .x-responsivecolumn-small {
- * > .small-100 {
- * @include responsivecolumn-item(100%);
- * }
- * }
- *
- * These can be added to components in the layout using the `responsiveCls` config:
- *
- * items: [{
- * xtype: 'my-panel',
- *
- * // Use 50% of space when viewport is "big" and 100% when viewport
- * // is "small":
- * responsiveCls: 'big-50 small-100'
- * }]
- *
- * The `responsiveCls` config is provided by this layout to avoid overwriting classes
- * specified using `cls` or other standard configs.
- *
- * Internally, this layout simply uses `float:left` and CSS `calc()` (except on IE8) to
- * "flex" each item. The calculation is always based on a percentage with a spacing taken
- * into account to separate the items from each other.
- */
- Ext.define('Ext.ux.layout.ResponsiveColumn', {
- extend: 'Ext.layout.container.Auto',
- alias: 'layout.responsivecolumn',
- /**
- * @cfg {Object} states
- *
- * A set of layout state names corresponding to viewport size thresholds. One of the
- * states will be used to assign the responsive column CSS class to the container to
- * trigger appropriate item sizing.
- *
- * For example:
- *
- * layout: {
- * type: 'responsivecolumn',
- * states: {
- * small: 800,
- * medium: 1200,
- * large: 0
- * }
- * }
- *
- * Given the above set of responsive states, one of the following CSS classes will be
- * added to the container:
- *
- * - `x-responsivecolumn-small` - If the viewport is <= 800px
- * - `x-responsivecolumn-medium` - If the viewport is > 800px and <= 1200px
- * - `x-responsivecolumn-large` - If the viewport is > 1200px
- *
- * For sake of efficiency these classes are based on the size of the browser viewport
- * (the browser window) and not on the container size. As the size of the viewport
- * changes, this layout will maintain the appropriate CSS class on the container which
- * will then activate the appropriate CSS rules to size the child items.
- */
- states: {
- small: 1000,
- large: 0
- },
- _responsiveCls: Ext.baseCSSPrefix + 'responsivecolumn',
- initLayout: function() {
- this.innerCtCls += ' ' + this._responsiveCls;
- this.callParent();
- },
- beginLayout: function(ownerContext) {
- var me = this,
- viewportWidth = Ext.Element.getViewportWidth(),
- states = me.states,
- activeThreshold = Infinity,
- innerCt = me.innerCt,
- currentState = me._currentState,
- name, threshold, newState;
- for (name in states) {
- threshold = states[name] || Infinity;
- if (viewportWidth <= threshold && threshold <= activeThreshold) {
- activeThreshold = threshold;
- newState = name;
- }
- }
- if (newState !== currentState) {
- innerCt.replaceCls(currentState, newState, me._responsiveCls);
- me._currentState = newState;
- }
- me.callParent(arguments);
- },
- onAdd: function(item) {
- var responsiveCls;
- this.callParent([
- item
- ]);
- responsiveCls = item.responsiveCls;
- if (responsiveCls) {
- item.addCls(responsiveCls);
- }
- }
- }, function(Responsive) {
- //--------------------------------------------------------------------------------------
- // IE8 does not support CSS calc expressions, so we have to fallback to more traditional
- // for of layout. This is very similar but much simpler than Column layout.
- //
- if (Ext.isIE8) {
- Responsive.override({
- responsiveSizePolicy: {
- readsWidth: 0,
- readsHeight: 0,
- setsWidth: 1,
- setsHeight: 0
- },
- setsItemSize: true,
- calculateItems: function(ownerContext, containerSize) {
- var me = this,
- targetContext = ownerContext.targetContext,
- items = ownerContext.childItems,
- len = items.length,
- gotWidth = containerSize.gotWidth,
- contentWidth = containerSize.width,
- i, itemContext, itemMarginWidth, itemWidth;
- // No parallel measurement, cannot lay out boxes.
- if (gotWidth === false) {
- targetContext.domBlock(me, 'width');
- return false;
- }
- if (!gotWidth) {
- // gotWidth is undefined, which means we must be width shrink wrap.
- // Cannot calculate item widths if we're shrink wrapping.
- return true;
- }
- for (i = 0; i < len; ++i) {
- itemContext = items[i];
- // The mixin encodes these in background-position syles since it is
- // unlikely a component will have a background-image.
- itemWidth = parseInt(itemContext.el.getStyle('background-position-x'), 10);
- itemMarginWidth = parseInt(itemContext.el.getStyle('background-position-y'), 10);
- itemContext.setWidth((itemWidth / 100 * (contentWidth - itemMarginWidth)) - itemMarginWidth);
- }
- ownerContext.setContentWidth(contentWidth + ownerContext.paddingContext.getPaddingInfo().width);
- return true;
- },
- getItemSizePolicy: function() {
- return this.responsiveSizePolicy;
- }
- });
- }
- });
- /**
- * A {@link Ext.ux.statusbar.StatusBar} plugin that provides automatic error
- * notification when the associated form contains validation errors.
- */
- Ext.define('Ext.ux.statusbar.ValidationStatus', {
- extend: 'Ext.Component',
- alias: 'plugin.validationstatus',
- requires: [
- 'Ext.util.MixedCollection'
- ],
- /**
- * @cfg {String} errorIconCls
- * The {@link Ext.ux.statusbar.StatusBar#iconCls iconCls} value to be applied
- * to the status message when there is a validation error.
- */
- errorIconCls: 'x-status-error',
- /**
- * @cfg {String} errorListCls
- * The css class to be used for the error list when there are validation errors.
- */
- errorListCls: 'x-status-error-list',
- /**
- * @cfg {String} validIconCls
- * The {@link Ext.ux.statusbar.StatusBar#iconCls iconCls} value to be applied
- * to the status message when the form validates.
- */
- validIconCls: 'x-status-valid',
- /**
- * @cfg {String} showText
- * The {@link Ext.ux.statusbar.StatusBar#text text} value to be applied when
- * there is a form validation error.
- */
- showText: 'The form has errors (click for details...)',
- /**
- * @cfg {String} hideText
- * The {@link Ext.ux.statusbar.StatusBar#text text} value to display when
- * the error list is displayed.
- */
- hideText: 'Click again to hide the error list',
- /**
- * @cfg {String} submitText
- * The {@link Ext.ux.statusbar.StatusBar#text text} value to be applied when
- * the form is being submitted.
- */
- submitText: 'Saving...',
- /**
- * @private
- */
- init: function(sb) {
- var me = this;
- me.statusBar = sb;
- sb.on({
- single: true,
- scope: me,
- render: me.onStatusbarRender
- });
- sb.on({
- click: {
- element: 'el',
- fn: me.onStatusClick,
- scope: me,
- buffer: 200
- }
- });
- },
- onStatusbarRender: function(sb) {
- var me = this,
- startMonitor = function() {
- me.monitor = true;
- };
- me.monitor = true;
- me.errors = Ext.create('Ext.util.MixedCollection');
- me.listAlign = (sb.statusAlign === 'right' ? 'br-tr?' : 'bl-tl?');
- if (me.form) {
- // Allow either an id, or a reference to be specified as the form name.
- me.formPanel = Ext.getCmp(me.form) || me.statusBar.lookupController().lookupReference(me.form);
- me.basicForm = me.formPanel.getForm();
- me.startMonitoring();
- me.basicForm.on({
- beforeaction: function(f, action) {
- if (action.type === 'submit') {
- // Ignore monitoring while submitting otherwise the field validation
- // events cause the status message to reset too early
- me.monitor = false;
- }
- }
- });
- me.formPanel.on({
- beforedestroy: me.destroy,
- scope: me
- });
- me.basicForm.on('actioncomplete', startMonitor);
- me.basicForm.on('actionfailed', startMonitor);
- }
- },
- /**
- * @private
- */
- startMonitoring: function() {
- this.basicForm.getFields().each(function(f) {
- f.on('validitychange', this.onFieldValidation, this);
- }, this);
- },
- /**
- * @private
- */
- stopMonitoring: function() {
- var form = this.basicForm;
- if (!form.destroyed) {
- form.getFields().each(function(f) {
- f.un('validitychange', this.onFieldValidation, this);
- }, this);
- }
- },
- doDestroy: function() {
- Ext.destroy(this.msgEl);
- this.stopMonitoring();
- this.statusBar.statusEl.un('click', this.onStatusClick, this);
- this.callParent();
- },
- /**
- * @private
- */
- onFieldValidation: function(f, isValid) {
- var me = this,
- msg;
- if (!me.monitor) {
- return false;
- }
- msg = f.getErrors()[0];
- if (msg) {
- me.errors.add(f.id, {
- field: f,
- msg: msg
- });
- } else {
- me.errors.removeAtKey(f.id);
- }
- this.updateErrorList();
- if (me.errors.getCount() > 0) {
- if (me.statusBar.getText() !== me.showText) {
- me.statusBar.setStatus({
- text: me.showText,
- iconCls: me.errorIconCls
- });
- }
- } else {
- me.statusBar.clearStatus().setIcon(me.validIconCls);
- }
- },
- /**
- * @private
- */
- updateErrorList: function() {
- var me = this,
- msg,
- msgEl = me.getMsgEl();
- if (me.errors.getCount() > 0) {
- msg = [
- '<ul>'
- ];
- this.errors.each(function(err) {
- msg.push('<li id="x-err-', err.field.id, '"><a href="#">', err.msg, '</a></li>');
- });
- msg.push('</ul>');
- msgEl.update(msg.join(''));
- } else {
- msgEl.update('');
- }
- // reset msgEl size
- msgEl.setSize('auto', 'auto');
- },
- /**
- * @private
- */
- getMsgEl: function() {
- var me = this,
- msgEl = me.msgEl,
- t;
- if (!msgEl) {
- msgEl = me.msgEl = Ext.DomHelper.append(Ext.getBody(), {
- cls: me.errorListCls
- }, true);
- msgEl.hide();
- msgEl.on('click', function(e) {
- t = e.getTarget('li', 10, true);
- if (t) {
- Ext.getCmp(t.id.split('x-err-')[1]).focus();
- me.hideErrors();
- }
- }, null, {
- stopEvent: true
- });
- }
- // prevent anchor click navigation
- return msgEl;
- },
- /**
- * @private
- */
- showErrors: function() {
- var me = this;
- me.updateErrorList();
- me.getMsgEl().alignTo(me.statusBar.getEl(), me.listAlign).slideIn('b', {
- duration: 300,
- easing: 'easeOut'
- });
- me.statusBar.setText(me.hideText);
- // hide if the user clicks directly into the form
- me.formPanel.body.on('click', me.hideErrors, me, {
- single: true
- });
- },
- /**
- * @private
- */
- hideErrors: function() {
- var el = this.getMsgEl();
- if (el.isVisible()) {
- el.slideOut('b', {
- duration: 300,
- easing: 'easeIn'
- });
- this.statusBar.setText(this.showText);
- }
- this.formPanel.body.un('click', this.hideErrors, this);
- },
- /**
- * @private
- */
- onStatusClick: function() {
- if (this.getMsgEl().isVisible()) {
- this.hideErrors();
- } else if (this.errors.getCount() > 0) {
- this.showErrors();
- }
- }
- });
|